From dc483ee0a2f66d0be76ed2274c69d2ac902b89fa Mon Sep 17 00:00:00 2001 From: imbeacon Date: Thu, 25 May 2023 13:16:43 +0300 Subject: [PATCH 001/168] Changed method for removing relations from all to removing only COMMON relations --- .../controller/EntityRelationController.java | 2 +- .../DefaultTbEntityRelationService.java | 4 ++-- .../relation/TbEntityRelationService.java | 2 +- .../server/dao/relation/RelationService.java | 2 ++ .../dao/relation/BaseRelationService.java | 22 +++++++++++++++++-- .../dao/service/BaseRelationServiceTest.java | 18 +++++++++++++++ 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index 08448f1d28..788b48360d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -129,7 +129,7 @@ public class EntityRelationController extends BaseController { checkParameter("entityType", strType); EntityId entityId = EntityIdFactory.getByTypeAndId(strType, strId); checkEntityId(entityId, Operation.WRITE); - tbEntityRelationService.deleteRelations(getTenantId(), getCurrentUser().getCustomerId(), entityId, getCurrentUser()); + tbEntityRelationService.deleteCommonRelations(getTenantId(), getCurrentUser().getCustomerId(), entityId, getCurrentUser()); } @ApiOperation(value = "Get Relation (getRelation)", diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java index b978b730fd..cf1733490d 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java @@ -72,9 +72,9 @@ public class DefaultTbEntityRelationService extends AbstractTbEntityService impl } @Override - public void deleteRelations(TenantId tenantId, CustomerId customerId, EntityId entityId, User user) throws ThingsboardException { + public void deleteCommonRelations(TenantId tenantId, CustomerId customerId, EntityId entityId, User user) throws ThingsboardException { try { - relationService.deleteEntityRelations(tenantId, entityId); + relationService.deleteEntityCommonRelations(tenantId, entityId); notificationEntityService.logEntityAction(tenantId, entityId, null, customerId, ActionType.RELATIONS_DELETED, user); } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, entityId, null, customerId, diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java index 2caee86d0c..8bf5018c95 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java @@ -28,6 +28,6 @@ public interface TbEntityRelationService { void delete(TenantId tenantId, CustomerId customerId, EntityRelation entity, User user) throws ThingsboardException; - void deleteRelations(TenantId tenantId, CustomerId customerId, EntityId entityId, User user) throws ThingsboardException; + void deleteCommonRelations(TenantId tenantId, CustomerId customerId, EntityId entityId, User user) throws ThingsboardException; } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java index 3ddb6187ea..39e94b3a6b 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java @@ -53,6 +53,8 @@ public interface RelationService { void deleteEntityRelations(TenantId tenantId, EntityId entity); + void deleteEntityCommonRelations(TenantId tenantId, EntityId entity); + List findByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup); ListenableFuture> findByFromAsync(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index fb86e09ab3..b0d5fb3c2c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -220,13 +220,31 @@ public class BaseRelationService implements RelationService { return future; } + @Transactional + @Override + public void deleteEntityCommonRelations(TenantId tenantId, EntityId entityId) { + deleteEntityRelations(tenantId, entityId, RelationTypeGroup.COMMON); + } + @Transactional @Override public void deleteEntityRelations(TenantId tenantId, EntityId entityId) { + deleteEntityRelations(tenantId, entityId, null); + } + + @Transactional + public void deleteEntityRelations(TenantId tenantId, EntityId entityId, RelationTypeGroup relationTypeGroup) { log.trace("Executing deleteEntityRelations [{}]", entityId); validate(entityId); - List inboundRelations = new ArrayList<>(relationDao.findAllByTo(tenantId, entityId)); - List outboundRelations = new ArrayList<>(relationDao.findAllByFrom(tenantId, entityId)); + List inboundRelations; + List outboundRelations; + if (relationTypeGroup == null) { + inboundRelations = relationDao.findAllByTo(tenantId, entityId); + outboundRelations = relationDao.findAllByFrom(tenantId, entityId); + } else { + inboundRelations = relationDao.findAllByFrom(tenantId, entityId, relationTypeGroup); + outboundRelations = relationDao.findAllByTo(tenantId, entityId, relationTypeGroup); + } if (!inboundRelations.isEmpty()) { try { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java index d60a896aec..ae07930156 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java @@ -130,6 +130,24 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); } + @Test + public void testDeleteEntityCommonRelations() { + AssetId parentId = new AssetId(Uuids.timeBased()); + AssetId childId = new AssetId(Uuids.timeBased()); + AssetId subChildId = new AssetId(Uuids.timeBased()); + + EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); + EntityRelation relationB = new EntityRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE); + + saveRelation(relationA); + saveRelation(relationB); + + relationService.deleteEntityCommonRelations(SYSTEM_TENANT_ID, childId); + + Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); + Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); + } + @Test public void testFindFrom() throws ExecutionException, InterruptedException { AssetId parentA = new AssetId(Uuids.timeBased()); From c9f5654e082b0f22ffa34387892428903eadde5d Mon Sep 17 00:00:00 2001 From: imbeacon Date: Thu, 25 May 2023 16:05:25 +0300 Subject: [PATCH 002/168] Added additional methods to dao to remove only required relations --- .../dao/relation/BaseRelationService.java | 27 +++++++++++-------- .../server/dao/relation/RelationDao.java | 4 +++ .../dao/sql/relation/JpaRelationDao.java | 23 +++++++++++++--- .../dao/sql/relation/RelationRepository.java | 5 ++++ .../dao/service/BaseRelationServiceTest.java | 7 +++++ 5 files changed, 51 insertions(+), 15 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index b0d5fb3c2c..f7ae4a1596 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -236,19 +236,20 @@ public class BaseRelationService implements RelationService { public void deleteEntityRelations(TenantId tenantId, EntityId entityId, RelationTypeGroup relationTypeGroup) { log.trace("Executing deleteEntityRelations [{}]", entityId); validate(entityId); - List inboundRelations; - List outboundRelations; - if (relationTypeGroup == null) { - inboundRelations = relationDao.findAllByTo(tenantId, entityId); - outboundRelations = relationDao.findAllByFrom(tenantId, entityId); - } else { - inboundRelations = relationDao.findAllByFrom(tenantId, entityId, relationTypeGroup); - outboundRelations = relationDao.findAllByTo(tenantId, entityId, relationTypeGroup); - } + List inboundRelations = relationTypeGroup == null + ? relationDao.findAllByTo(tenantId, entityId) + : relationDao.findAllByTo(tenantId, entityId, relationTypeGroup); + List outboundRelations = relationTypeGroup == null + ? relationDao.findAllByFrom(tenantId, entityId) + : relationDao.findAllByFrom(tenantId, entityId, relationTypeGroup); if (!inboundRelations.isEmpty()) { try { - relationDao.deleteInboundRelations(tenantId, entityId); + if (relationTypeGroup == null) { + relationDao.deleteInboundRelations(tenantId, entityId); + } else { + relationDao.deleteInboundRelations(tenantId, entityId, relationTypeGroup); + } } catch (ConcurrencyFailureException e) { log.debug("Concurrency exception while deleting relations [{}]", inboundRelations, e); } @@ -259,7 +260,11 @@ public class BaseRelationService implements RelationService { } if (!outboundRelations.isEmpty()) { - relationDao.deleteOutboundRelations(tenantId, entityId); + if (relationTypeGroup == null) { + relationDao.deleteOutboundRelations(tenantId, entityId); + } else { + relationDao.deleteOutboundRelations(tenantId, entityId, relationTypeGroup); + } for (EntityRelation relation : outboundRelations) { eventPublisher.publishEvent(EntityRelationEvent.from(relation)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java index 7fee4a31ff..250a0c6105 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java @@ -64,8 +64,12 @@ public interface RelationDao { void deleteOutboundRelations(TenantId tenantId, EntityId entity); + void deleteOutboundRelations(TenantId tenantId, EntityId entity, RelationTypeGroup relationTypeGroup); + void deleteInboundRelations(TenantId tenantId, EntityId entity); + void deleteInboundRelations(TenantId tenantId, EntityId entity, RelationTypeGroup relationTypeGroup); + ListenableFuture deleteOutboundRelationsAsync(TenantId tenantId, EntityId entity); List findRuleNodeToRuleChainRelations(RuleChainType ruleChainType, int limit); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java index c1b17f160e..7e4b41702e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java @@ -34,10 +34,7 @@ import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; import org.thingsboard.server.dao.util.SqlDao; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; /** @@ -205,6 +202,15 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple } } + @Override + public void deleteOutboundRelations(TenantId tenantId, EntityId entity, RelationTypeGroup relationTypeGroup) { + try { + relationRepository.deleteByFromIdAndFromTypeAndRelationTypeGroupIn(entity.getId(), entity.getEntityType().name(), Collections.singletonList(relationTypeGroup.name())); + } catch (ConcurrencyFailureException e) { + log.debug("Concurrency exception while deleting relations [{}]", entity, e); + } + } + @Override public void deleteInboundRelations(TenantId tenantId, EntityId entity) { try { @@ -214,6 +220,15 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple } } + @Override + public void deleteInboundRelations(TenantId tenantId, EntityId entity, RelationTypeGroup relationTypeGroup) { + try { + relationRepository.deleteByToIdAndToTypeAndRelationTypeGroupIn(entity.getId(), entity.getEntityType().name(), Collections.singletonList(relationTypeGroup.name())); + } catch (ConcurrencyFailureException e) { + log.debug("Concurrency exception while deleting relations [{}]", entity, e); + } + } + @Override public ListenableFuture deleteOutboundRelationsAsync(TenantId tenantId, EntityId entity) { return service.submit( diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java index a3d6d8570d..10c8c826eb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java @@ -82,4 +82,9 @@ public interface RelationRepository @Query("DELETE FROM RelationEntity r where r.toId = :toId and r.toType = :toType and r.relationTypeGroup in :relationTypeGroups") void deleteByToIdAndToTypeAndRelationTypeGroupIn(@Param("toId") UUID toId, @Param("toType") String toType, @Param("relationTypeGroups") List relationTypeGroups); + @Transactional + @Modifying + @Query("DELETE FROM RelationEntity r where r.fromId = :fromId and r.fromType = :fromType and r.relationTypeGroup in :relationTypeGroups") + void deleteByFromIdAndFromTypeAndRelationTypeGroupIn(@Param("fromId") UUID fromId, @Param("fromType") String fromType, @Param("relationTypeGroups") List relationTypeGroups); + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java index ae07930156..afd42c5ab8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java @@ -138,14 +138,21 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); EntityRelation relationB = new EntityRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE); + EntityRelation relationC = new EntityRelation(parentId, childId, EntityRelation.MANAGES_TYPE, RelationTypeGroup.EDGE); + EntityRelation relationD = new EntityRelation(childId, subChildId, EntityRelation.MANAGES_TYPE, RelationTypeGroup.EDGE); saveRelation(relationA); saveRelation(relationB); + saveRelation(relationC); + saveRelation(relationD); relationService.deleteEntityCommonRelations(SYSTEM_TENANT_ID, childId); Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); + + Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.MANAGES_TYPE, RelationTypeGroup.EDGE)); + Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.MANAGES_TYPE, RelationTypeGroup.EDGE)); } @Test From 91aca058554615f58de5a72447b921ef565a6c91 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Thu, 25 May 2023 17:00:24 +0300 Subject: [PATCH 003/168] Imports --- .../thingsboard/server/dao/sql/relation/JpaRelationDao.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java index 7e4b41702e..31125a0791 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java @@ -34,7 +34,11 @@ import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; import org.thingsboard.server.dao.util.SqlDao; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.stream.Collectors; /** From f4404d20f0aeb6e57a6f12cfecf528e6f746f693 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Mon, 29 May 2023 16:17:33 +0300 Subject: [PATCH 004/168] Updated due to comments --- .../server/controller/EntityRelationController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index 788b48360d..e1e89734b9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -117,8 +117,8 @@ public class EntityRelationController extends BaseController { tbEntityRelationService.delete(getTenantId(), getCurrentUser().getCustomerId(), relation, getCurrentUser()); } - @ApiOperation(value = "Delete Relations (deleteRelations)", - notes = "Deletes all the relation (both 'from' and 'to' direction) for the specified entity. " + + @ApiOperation(value = "Delete common relations (deleteCommonRelations)", + notes = "Deletes all the relations ('from' and 'to' direction) for the specified entity and relation type group: 'COMMON'. " + SECURITY_CHECKS_ENTITY_DESCRIPTION) @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.DELETE, params = {"entityId", "entityType"}) From 684e0159e1c1a630dd163e25aba33cf76728c697 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Wed, 7 Jun 2023 08:11:19 +0300 Subject: [PATCH 005/168] Added unassign for alarms on user removal --- .../entitiy/alarm/DefaultTbAlarmService.java | 43 +++++++++++++++++++ .../service/entitiy/alarm/TbAlarmService.java | 3 ++ .../entitiy/user/DefaultUserService.java | 3 ++ 3 files changed, 49 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index caf65d39ec..141917f11f 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmCommentType; import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmQueryV2; import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -36,9 +37,13 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; @Service @AllArgsConstructor @@ -210,6 +215,44 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb return alarmInfo; } + @Override + public void unassignUserAlarms(TenantId tenantId, User user, long unassignTs) throws ThingsboardException { + AlarmQueryV2 alarmQuery = AlarmQueryV2.builder().assigneeId(user.getId()).pageLink(new TimePageLink(Integer.MAX_VALUE)).build(); + try { + List alarms = alarmService.findAlarmsV2(tenantId, alarmQuery).get(30, TimeUnit.SECONDS).getData(); + if (alarms.isEmpty()) { + throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); + } + for (AlarmInfo alarm : alarms) { + AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(tenantId, alarm.getId(), getOrDefault(unassignTs)); + if (!result.isSuccessful()) { + continue; + } + if (result.isModified()) { + AlarmComment alarmComment = AlarmComment.builder() + .alarmId(alarm.getId()) + .type(AlarmCommentType.SYSTEM) + .comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was unassigned because user %s - was deleted", + (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName())) + .put("userId", user.getId().toString()) + .put("subtype", "ASSIGN")) + .build(); + try { + alarmCommentService.saveAlarmComment(alarm, alarmComment, user); + } catch (ThingsboardException e) { + log.error("Failed to save alarm comment", e); + } + notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_UNASSIGNED, user); + } else { + throw new ThingsboardException("Alarm was already unassigned!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + } + + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + } + @Override public Boolean delete(Alarm alarm, User user) { TenantId tenantId = alarm.getTenantId(); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java index a2ae9c8cc7..ed4af5d1bd 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java @@ -19,6 +19,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; public interface TbAlarmService { @@ -37,5 +38,7 @@ public interface TbAlarmService { AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException; + void unassignUserAlarms(TenantId tenantId, User user, long unassignTs) throws ThingsboardException; + Boolean delete(Alarm alarm, User user); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index c83c48cc8a..0c04e46ff5 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; +import org.thingsboard.server.service.entitiy.alarm.TbAlarmService; import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; @@ -43,6 +44,7 @@ import static org.thingsboard.server.controller.UserController.ACTIVATE_URL_PATT public class DefaultUserService extends AbstractTbEntityService implements TbUserService { private final UserService userService; + private final TbAlarmService tbAlarmService; private final MailService mailService; private final SystemSecurityService systemSecurityService; @@ -80,6 +82,7 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse UserId userId = tbUser.getId(); try { + tbAlarmService.unassignUserAlarms(tenantId, tbUser, System.currentTimeMillis()); userService.deleteUser(tenantId, userId); notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, customerId, userId, tbUser, user, ActionType.DELETED, true, null, customerId.toString()); From 11f897d9b126e60e4693d1c0615963fadf7c6b26 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Wed, 7 Jun 2023 10:03:17 +0300 Subject: [PATCH 006/168] Added test --- .../controller/AlarmControllerTest.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java index 05030f0091..c6b31ce683 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java @@ -32,6 +32,7 @@ import org.springframework.test.context.ContextConfiguration; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmSeverity; @@ -39,6 +40,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.service.DaoSqlTest; @@ -529,6 +531,41 @@ public class AlarmControllerTest extends AbstractControllerTest { tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_UNASSIGNED); } + @Test + public void testUnassignAlarmOnUserRemoving() throws Exception { + loginTenantAdmin(); + + + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(tenantId); + user.setEmail("tenantForAssign@thingsboard.org"); + User savedUser = createUser(user, "password"); + + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + Mockito.reset(tbClusterService, auditLogService); + long beforeAssignmentTs = System.currentTimeMillis(); + Thread.sleep(2); + + doPost("/api/alarm/" + alarm.getId() + "/assign/" + savedUser.getId().getId()).andExpect(status().isOk()); + AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); + Assert.assertNotNull(foundAlarm); + Assert.assertEquals(savedUser.getId(), foundAlarm.getAssigneeId()); + Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis()); + + beforeAssignmentTs = System.currentTimeMillis(); + + Mockito.reset(tbClusterService, auditLogService); + + doDelete("/api/user/" + savedUser.getId().getId()).andExpect(status().isOk()); + Thread.sleep(2); + + foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); + Assert.assertNotNull(foundAlarm); + Assert.assertNull(foundAlarm.getAssigneeId()); + Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis()); + } + @Test public void testFindAlarmsViaCustomerUser() throws Exception { loginCustomerUser(); From 505acb560f17beb2f74c7b922c8fa840ea6d5df1 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Fri, 9 Jun 2023 10:22:00 +0300 Subject: [PATCH 007/168] Removed exception throw on alarms not found for user on deleting --- .../server/service/entitiy/alarm/DefaultTbAlarmService.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index 141917f11f..fc3198a736 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -220,9 +220,6 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb AlarmQueryV2 alarmQuery = AlarmQueryV2.builder().assigneeId(user.getId()).pageLink(new TimePageLink(Integer.MAX_VALUE)).build(); try { List alarms = alarmService.findAlarmsV2(tenantId, alarmQuery).get(30, TimeUnit.SECONDS).getData(); - if (alarms.isEmpty()) { - throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); - } for (AlarmInfo alarm : alarms) { AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(tenantId, alarm.getId(), getOrDefault(unassignTs)); if (!result.isSuccessful()) { From 5ddb62322ccab6a147d88c8f59d4d5cf68251c78 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Fri, 9 Jun 2023 13:47:51 +0300 Subject: [PATCH 008/168] Updated test --- .../thingsboard/server/controller/AlarmControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java index c6b31ce683..f83deaad2e 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java @@ -551,7 +551,7 @@ public class AlarmControllerTest extends AbstractControllerTest { AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); Assert.assertNotNull(foundAlarm); Assert.assertEquals(savedUser.getId(), foundAlarm.getAssigneeId()); - Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis()); + Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs); beforeAssignmentTs = System.currentTimeMillis(); @@ -563,7 +563,7 @@ public class AlarmControllerTest extends AbstractControllerTest { foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); Assert.assertNotNull(foundAlarm); Assert.assertNull(foundAlarm.getAssigneeId()); - Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis()); + Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs); } @Test From b33c39dd253215aec3652ddcf955d8ce78158361 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Thu, 15 Jun 2023 11:58:48 +0300 Subject: [PATCH 009/168] Refactoring --- .../org/thingsboard/server/controller/AlarmControllerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java index f83deaad2e..dccb650555 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java @@ -535,7 +535,6 @@ public class AlarmControllerTest extends AbstractControllerTest { public void testUnassignAlarmOnUserRemoving() throws Exception { loginTenantAdmin(); - User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(tenantId); From 7e5069d78452ee48217bbeb22d08476e0ba7ff59 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Fri, 16 Jun 2023 09:26:19 +0300 Subject: [PATCH 010/168] Changed test to work with different tenant, to avoid affecting by other tests --- .../server/controller/AlarmControllerTest.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java index dccb650555..c2e34c5d2e 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java @@ -533,7 +533,7 @@ public class AlarmControllerTest extends AbstractControllerTest { @Test public void testUnassignAlarmOnUserRemoving() throws Exception { - loginTenantAdmin(); + loginDifferentTenant(); User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); @@ -541,7 +541,20 @@ public class AlarmControllerTest extends AbstractControllerTest { user.setEmail("tenantForAssign@thingsboard.org"); User savedUser = createUser(user, "password"); - Alarm alarm = createAlarm(TEST_ALARM_TYPE); + Device device = createDevice("Different tenant device", "default", "differentTenantTest"); + + Alarm alarm = Alarm.builder() + .type(TEST_ALARM_TYPE) + .tenantId(differentTenantId) + .originator(device.getId()) + .severity(AlarmSeverity.MAJOR) + .build(); + alarm = doPost("/api/alarm", alarm, Alarm.class); + Assert.assertNotNull(alarm); + + alarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); + Assert.assertNotNull(alarm); + Mockito.reset(tbClusterService, auditLogService); long beforeAssignmentTs = System.currentTimeMillis(); Thread.sleep(2); From b8e12a46217e6359250b645b33b364d54c39d0a3 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Fri, 16 Jun 2023 10:38:55 +0300 Subject: [PATCH 011/168] Refactoring --- .../server/service/entitiy/alarm/DefaultTbAlarmService.java | 4 +--- .../server/service/entitiy/alarm/TbAlarmService.java | 2 +- .../thingsboard/server/controller/AlarmControllerTest.java | 6 ++---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index fc3198a736..07c66e359a 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -216,7 +216,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } @Override - public void unassignUserAlarms(TenantId tenantId, User user, long unassignTs) throws ThingsboardException { + public void unassignUserAlarms(TenantId tenantId, User user, long unassignTs) { AlarmQueryV2 alarmQuery = AlarmQueryV2.builder().assigneeId(user.getId()).pageLink(new TimePageLink(Integer.MAX_VALUE)).build(); try { List alarms = alarmService.findAlarmsV2(tenantId, alarmQuery).get(30, TimeUnit.SECONDS).getData(); @@ -240,8 +240,6 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb log.error("Failed to save alarm comment", e); } notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_UNASSIGNED, user); - } else { - throw new ThingsboardException("Alarm was already unassigned!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java index ed4af5d1bd..24af185539 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java @@ -38,7 +38,7 @@ public interface TbAlarmService { AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException; - void unassignUserAlarms(TenantId tenantId, User user, long unassignTs) throws ThingsboardException; + void unassignUserAlarms(TenantId tenantId, User user, long unassignTs); Boolean delete(Alarm alarm, User user); } diff --git a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java index c2e34c5d2e..8f28ca0110 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java @@ -557,25 +557,23 @@ public class AlarmControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); long beforeAssignmentTs = System.currentTimeMillis(); - Thread.sleep(2); doPost("/api/alarm/" + alarm.getId() + "/assign/" + savedUser.getId().getId()).andExpect(status().isOk()); AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); Assert.assertNotNull(foundAlarm); Assert.assertEquals(savedUser.getId(), foundAlarm.getAssigneeId()); - Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs); + Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs); beforeAssignmentTs = System.currentTimeMillis(); Mockito.reset(tbClusterService, auditLogService); doDelete("/api/user/" + savedUser.getId().getId()).andExpect(status().isOk()); - Thread.sleep(2); foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); Assert.assertNotNull(foundAlarm); Assert.assertNull(foundAlarm.getAssigneeId()); - Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs); + Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs); } @Test From aa28b276d23210308b93959669ab71de0f0fd4dc Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 16 Jun 2023 15:40:29 +0200 Subject: [PATCH 012/168] added recalculetePartitions delay for node restart --- .../src/main/resources/thingsboard.yml | 1 + .../queue/discovery/ZkDiscoveryService.java | 31 ++++++++++++++++++- .../src/main/resources/tb-vc-executor.yml | 1 + .../src/main/resources/tb-coap-transport.yml | 1 + .../src/main/resources/tb-http-transport.yml | 1 + .../src/main/resources/tb-lwm2m-transport.yml | 1 + .../src/main/resources/tb-mqtt-transport.yml | 1 + .../src/main/resources/tb-snmp-transport.yml | 1 + 8 files changed, 37 insertions(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index e7fbbd2a3d..9cec475335 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -96,6 +96,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cluster: stats: diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index fcf80bcf3d..17d046a4cb 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -44,8 +44,10 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.List; import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; 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; @@ -66,6 +68,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private Integer zkSessionTimeout; @Value("${zk.zk_dir}") private String zkDir; + @Value("${zk.recalculate_delay:120000}") + private Long recalculateDelay; + + private final ConcurrentHashMap> delayedTasks; private final TbServiceInfoProvider serviceInfoProvider; private final PartitionService partitionService; @@ -82,6 +88,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi PartitionService partitionService) { this.serviceInfoProvider = serviceInfoProvider; this.partitionService = partitionService; + delayedTasks = new ConcurrentHashMap<>(); } @PostConstruct @@ -290,8 +297,30 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi log.debug("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), instance.getServiceId()); switch (pathChildrenCacheEvent.getType()) { case CHILD_ADDED: + ScheduledFuture task = delayedTasks.remove(instance.getServiceId()); + if (task != null) { + if (!task.cancel(false)) { + log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + recalculatePartitions(); + } else { + log.debug("[{}] Recalculate partitions ignored. Service restarted in time [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + } + } else { + log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + recalculatePartitions(); + } + break; case CHILD_REMOVED: - recalculatePartitions(); + ScheduledFuture future = zkExecutorService.schedule(() -> { + log.debug("[{}] Going to recalculate partitions due to removed node [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + delayedTasks.remove(instance.getServiceId()); + recalculatePartitions(); + }, recalculateDelay, TimeUnit.MILLISECONDS); + delayedTasks.put(instance.getServiceId(), future); break; default: break; diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index 094e0e2099..2c90082eb5 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" queue: type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index b9db930657..aef46a1234 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index bff7adb561..4bce6e28d7 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -68,6 +68,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index ae8f0138a7..eab5b107c8 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index 076dde0234..f0968aa6b9 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index c68c9c56a8..c7dcd70574 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" From ce103cf3396614c5a151c9bfd74c23e4fcbade05 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Mon, 19 Jun 2023 07:27:44 +0300 Subject: [PATCH 013/168] changed test to get id of savedDifferentTenant --- .../org/thingsboard/server/controller/AlarmControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java index 8f28ca0110..50761be096 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java @@ -545,7 +545,7 @@ public class AlarmControllerTest extends AbstractControllerTest { Alarm alarm = Alarm.builder() .type(TEST_ALARM_TYPE) - .tenantId(differentTenantId) + .tenantId(savedDifferentTenant.getId()) .originator(device.getId()) .severity(AlarmSeverity.MAJOR) .build(); From 22874e8a65c1a353bc639be00f005a75030b9be9 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 19 Jun 2023 17:55:39 +0300 Subject: [PATCH 014/168] filter nodes && added TbMsgType enum --- .../actors/ruleChain/DefaultTbContext.java | 26 ++--- .../RuleChainActorMessageProcessor.java | 4 +- .../server/controller/RpcV2Controller.java | 4 +- .../service/action/EntityActionService.java | 94 ++---------------- .../AnnotationComponentDiscoveryService.java | 25 +++-- .../device/DeviceProvisionServiceImpl.java | 24 +++-- .../service/edge/rpc/EdgeGrpcService.java | 10 +- .../processor/device/DeviceEdgeProcessor.java | 6 +- .../telemetry/BaseTelemetryProcessor.java | 4 +- .../DefaultTbNotificationEntityService.java | 5 +- .../rpc/DefaultTbCoreDeviceRpcService.java | 5 +- .../state/DefaultDeviceStateService.java | 18 ++-- .../transport/DefaultTransportApiService.java | 3 +- .../server/common/data/DataConstants.java | 44 --------- .../server/common/data/EntityType.java | 8 ++ .../server/common/data/audit/ActionType.java | 88 +++++++++-------- .../server/common/data/msg/TbMsgType.java | 95 +++++++++++++++++++ .../engine/api/EmptyNodeConfiguration.java | 3 +- ...onTypes.java => TbNodeConnectionType.java} | 9 +- .../engine/action/TbAbstractAlarmNode.java | 15 ++- .../action/TbAbstractRelationActionNode.java | 4 +- .../TbCopyAttributesToEntityViewNode.java | 22 +++-- .../rule/engine/action/TbMsgCountNode.java | 2 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 2 +- .../deduplication/TbMsgDeduplicationNode.java | 4 +- .../rule/engine/delay/TbMsgDelayNode.java | 2 +- .../engine/edge/AbstractTbMsgPushNode.java | 52 ++++++---- .../engine/filter/TbAssetTypeSwitchNode.java | 3 +- .../engine/filter/TbCheckAlarmStatusNode.java | 37 +++----- .../filter/TbCheckAlarmStatusNodeConfig.java | 4 +- .../engine/filter/TbCheckMessageNode.java | 13 +-- .../engine/filter/TbCheckRelationNode.java | 29 +++--- .../engine/filter/TbDeviceTypeSwitchNode.java | 3 +- .../rule/engine/filter/TbJsFilterNode.java | 9 +- .../rule/engine/filter/TbJsSwitchNode.java | 3 +- .../engine/filter/TbMsgTypeFilterNode.java | 8 +- .../engine/filter/TbMsgTypeSwitchNode.java | 86 +---------------- .../filter/TbOriginatorTypeFilterNode.java | 8 +- .../filter/TbOriginatorTypeSwitchNode.java | 50 +--------- .../rule/engine/flow/TbCheckpointNode.java | 4 +- .../engine/geo/TbGpsGeofencingFilterNode.java | 8 +- .../rule/engine/kafka/TbKafkaNode.java | 4 +- .../rule/engine/mail/TbMsgToEmailNode.java | 3 +- .../TbAbstractGetEntityDetailsNode.java | 2 +- .../rule/engine/profile/DeviceState.java | 31 ++++-- .../engine/profile/TbDeviceProfileNode.java | 8 +- .../rule/engine/rest/TbHttpClient.java | 4 +- .../rule/engine/rpc/TbSendRPCRequestNode.java | 7 +- .../transform/TbAbstractTransformNode.java | 4 +- .../engine/transform/TbSplitArrayMsgNode.java | 4 +- .../action/TbCreateRelationNodeTest.java | 13 +-- .../engine/edge/TbMsgPushToEdgeNodeTest.java | 18 ++-- .../engine/filter/TbJsFilterNodeTest.java | 14 +-- .../rule/engine/profile/DeviceStateTest.java | 15 +-- .../transform/TbMsgDeduplicationNodeTest.java | 12 +-- 55 files changed, 464 insertions(+), 518 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java rename rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/{TbRelationTypes.java => TbNodeConnectionType.java} (74%) 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 8b76924d96..778a3db51f 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 @@ -15,7 +15,6 @@ */ package org.thingsboard.server.actors.ruleChain; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.netty.channel.EventLoopGroup; @@ -34,7 +33,7 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.util.TenantIdLoader; @@ -42,7 +41,6 @@ import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; @@ -114,6 +112,10 @@ import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; + /** * Created by ashvayka on 19.03.18. */ @@ -132,7 +134,7 @@ class DefaultTbContext implements TbContext { @Override public void tellSuccess(TbMsg msg) { - tellNext(msg, Collections.singleton(TbRelationTypes.SUCCESS), null); + tellNext(msg, Collections.singleton(TbNodeConnectionType.SUCCESS), null); } @Override @@ -211,7 +213,7 @@ class DefaultTbContext implements TbContext { @Override public void enqueueForTellFailure(TbMsg tbMsg, String failureMessage) { TopicPartitionInfo tpi = resolvePartition(tbMsg); - enqueueForTellNext(tpi, tbMsg, Collections.singleton(TbRelationTypes.FAILURE), failureMessage, null, null); + enqueueForTellNext(tpi, tbMsg, Collections.singleton(TbNodeConnectionType.FAILURE), failureMessage, null, null); } @Override @@ -309,7 +311,7 @@ class DefaultTbContext implements TbContext { @Override public void tellFailure(TbMsg msg, Throwable th) { if (nodeCtx.getSelf().isDebugMode()) { - mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, TbRelationTypes.FAILURE, th); + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, TbNodeConnectionType.FAILURE, th); } String failureMessage; if (th != null) { @@ -322,7 +324,7 @@ class DefaultTbContext implements TbContext { failureMessage = null; } nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getRuleChainId(), - nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE), + nodeCtx.getSelf().getId(), Collections.singleton(TbNodeConnectionType.FAILURE), msg, failureMessage)); } @@ -346,7 +348,7 @@ class DefaultTbContext implements TbContext { } public TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId) { - return entityActionMsg(customer, customer.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); + return entityActionMsg(customer, customer.getId(), ruleNodeId, ENTITY_CREATED.name()); } public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { @@ -354,7 +356,7 @@ class DefaultTbContext implements TbContext { if (device.getDeviceProfileId() != null) { deviceProfile = mainCtx.getDeviceProfileCache().find(device.getDeviceProfileId()); } - return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED, deviceProfile); + return entityActionMsg(device, device.getId(), ruleNodeId, ENTITY_CREATED.name(), deviceProfile); } public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { @@ -362,7 +364,7 @@ class DefaultTbContext implements TbContext { if (asset.getAssetProfileId() != null) { assetProfile = mainCtx.getAssetProfileCache().find(asset.getAssetProfileId()); } - return entityActionMsg(asset, asset.getId(), ruleNodeId, DataConstants.ENTITY_CREATED, assetProfile); + return entityActionMsg(asset, asset.getId(), ruleNodeId, ENTITY_CREATED.name(), assetProfile); } public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { @@ -382,7 +384,7 @@ class DefaultTbContext implements TbContext { if (attributes != null) { attributes.forEach(attributeKvEntry -> JacksonUtil.addKvEntry(entityNode, attributeKvEntry)); } - return attributesActionMsg(originator, ruleNodeId, scope, DataConstants.ATTRIBUTES_UPDATED, JacksonUtil.toString(entityNode)); + return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_UPDATED.name(), JacksonUtil.toString(entityNode)); } public TbMsg attributesDeletedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List keys) { @@ -391,7 +393,7 @@ class DefaultTbContext implements TbContext { if (keys != null) { keys.forEach(attrsArrayNode::add); } - return attributesActionMsg(originator, ruleNodeId, scope, DataConstants.ATTRIBUTES_DELETED, JacksonUtil.toString(entityNode)); + return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_DELETED.name(), JacksonUtil.toString(entityNode)); } private TbMsg attributesActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, String action, String msgData) { 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 4bd082ee38..351a012d73 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,7 @@ package org.thingsboard.server.actors.ruleChain; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorRef; @@ -307,7 +307,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor msgType = actionType.getRuleEngineMsgType(); + if (msgType.isPresent()) { try { TbMsgMetaData metaData = new TbMsgMetaData(); if (user != null) { @@ -247,7 +171,7 @@ public class EntityActionService { if (tenantId != null && !tenantId.isSysTenantId()) { processNotificationRules(tenantId, entityId, entity, actionType, user, additionalInfo); } - TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); + TbMsg tbMsg = TbMsg.newMsg(msgType.get().name(), entityId, customerId, metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null); } catch (Exception e) { log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e); diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index ad65ee805b..e8fff7ba60 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -30,8 +30,12 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeDefinition; import org.thingsboard.rule.engine.api.RuleNode; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbVersionedNode; +import org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode; +import org.thingsboard.rule.engine.filter.TbOriginatorTypeSwitchNode; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -194,7 +198,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe scannedComponent.setName(ruleNodeAnnotation.name()); scannedComponent.setScope(ruleNodeAnnotation.scope()); scannedComponent.setClusteringMode(ruleNodeAnnotation.clusteringMode()); - NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation); + NodeDefinition nodeDefinition = prepareNodeDefinition(clazz, ruleNodeAnnotation); ObjectNode configurationDescriptor = JacksonUtil.newObjectNode(); JsonNode node = JacksonUtil.valueToTree(nodeDefinition); configurationDescriptor.set("nodeDefinition", node); @@ -221,13 +225,13 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe return scannedComponent; } - private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws Exception { + private NodeDefinition prepareNodeDefinition(Class clazz, RuleNode nodeAnnotation) throws Exception { NodeDefinition nodeDefinition = new NodeDefinition(); nodeDefinition.setDetails(nodeAnnotation.nodeDetails()); nodeDefinition.setDescription(nodeAnnotation.nodeDescription()); nodeDefinition.setInEnabled(nodeAnnotation.inEnabled()); nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled()); - nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(nodeAnnotation)); + nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(clazz, nodeAnnotation)); nodeDefinition.setCustomRelations(nodeAnnotation.customRelations()); nodeDefinition.setRuleChainNode(nodeAnnotation.ruleChainNode()); Class configClazz = nodeAnnotation.configClazz(); @@ -242,10 +246,17 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe return nodeDefinition; } - private String[] getRelationTypesWithFailureRelation(RuleNode nodeAnnotation) { + private String[] getRelationTypesWithFailureRelation(Class clazz, RuleNode nodeAnnotation) { List relationTypes = new ArrayList<>(Arrays.asList(nodeAnnotation.relationTypes())); - if (!relationTypes.contains(TbRelationTypes.FAILURE)) { - relationTypes.add(TbRelationTypes.FAILURE); + if (TbOriginatorTypeSwitchNode.class.equals(clazz)) { + relationTypes.addAll(EntityType.NORMAL_NAMES); + } + if (TbMsgTypeSwitchNode.class.equals(clazz)) { + relationTypes.addAll(TbMsgType.NODE_CONNECTIONS); + relationTypes.add(TbMsgType.OTHER); + } + if (!relationTypes.contains(TbNodeConnectionType.FAILURE)) { + relationTypes.add(TbNodeConnectionType.FAILURE); } return relationTypes.toArray(new String[relationTypes.size()]); } diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index beecebe2ba..3bed8a1905 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -23,7 +23,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; @@ -69,6 +68,11 @@ import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_FAILURE; +import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_SUCCESS; + @Service @Slf4j @@ -162,7 +166,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) { if (targetDevice != null) { log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); - notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false); + notify(targetDevice, provisionRequest, PROVISION_FAILURE.name(), false); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } else { return createDevice(provisionRequest, targetProfile); @@ -188,13 +192,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private ProvisionResponse processProvision(Device device, ProvisionRequest provisionRequest) { try { Optional provisionState = attributesService.find(device.getTenantId(), device.getId(), - DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); + SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); if (provisionState != null && provisionState.isPresent() && !provisionState.get().getValueAsString().equals(PROVISIONED_STATE)) { - notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + notify(device, provisionRequest, PROVISION_FAILURE.name(), false); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } else { saveProvisionStateAttribute(device).get(); - notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true); + notify(device, provisionRequest, PROVISION_SUCCESS.name(), true); } } catch (InterruptedException | ExecutionException e) { throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); @@ -222,14 +226,14 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { clusterService.onDeviceUpdated(savedDevice, null); saveProvisionStateAttribute(savedDevice).get(); pushDeviceCreatedEventToRuleEngine(savedDevice); - notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); + notify(savedDevice, provisionRequest, PROVISION_SUCCESS.name(), true); return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS); } catch (Exception e) { log.warn("[{}] Error during device creation from provision request: [{}]", provisionRequest.getDeviceName(), provisionRequest, e); Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); if (device != null) { - notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + notify(device, provisionRequest, PROVISION_FAILURE.name(), false); } throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } @@ -244,7 +248,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } private ListenableFuture> saveProvisionStateAttribute(Device device) { - return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, + return attributesService.save(device.getTenantId(), device.getId(), SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE), System.currentTimeMillis()))); } @@ -266,10 +270,10 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private void pushDeviceCreatedEventToRuleEngine(Device device) { try { ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); - TbMsg msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); + TbMsg msg = TbMsg.newMsg(ENTITY_CREATED.name(), device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); sendToRuleEngine(device.getTenantId(), msg, null); } catch (JsonProcessingException | IllegalArgumentException e) { - log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e); + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), ENTITY_CREATED, e); } } 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 b7334ebf5a..233dcba2cc 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 @@ -71,9 +71,9 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; -import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT; -import static org.thingsboard.server.common.data.DataConstants.DISCONNECT_EVENT; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; @Service @Slf4j @@ -278,7 +278,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, true); long lastConnectTs = System.currentTimeMillis(); save(edgeId, DefaultDeviceStateService.LAST_CONNECT_TIME, lastConnectTs); - pushRuleEngineMessage(edgeGrpcSession.getEdge().getTenantId(), edgeId, lastConnectTs, CONNECT_EVENT); + pushRuleEngineMessage(edgeGrpcSession.getEdge().getTenantId(), edgeId, lastConnectTs, CONNECT_EVENT.name()); cancelScheduleEdgeEventsCheck(edgeId); scheduleEdgeEventsCheck(edgeGrpcSession); } @@ -395,7 +395,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, false); long lastDisconnectTs = System.currentTimeMillis(); save(edgeId, DefaultDeviceStateService.LAST_DISCONNECT_TIME, lastDisconnectTs); - pushRuleEngineMessage(toRemove.getEdge().getTenantId(), edgeId, lastDisconnectTs, DISCONNECT_EVENT); + pushRuleEngineMessage(toRemove.getEdge().getTenantId(), edgeId, lastDisconnectTs, DISCONNECT_EVENT.name()); cancelScheduleEdgeEventsCheck(edgeId); } else { log.debug("[{}] edge session [{}] is not available anymore, nothing to remove. most probably this session is already outdated!", edgeId, sessionId); @@ -451,7 +451,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i private void pushRuleEngineMessage(TenantId tenantId, EdgeId edgeId, long ts, String msgType) { try { ObjectNode edgeState = JacksonUtil.newObjectNode(); - if (msgType.equals(CONNECT_EVENT)) { + if (msgType.equals(CONNECT_EVENT.name())) { edgeState.put(DefaultDeviceStateService.ACTIVITY_STATE, true); edgeState.put(DefaultDeviceStateService.LAST_CONNECT_TIME, ts); } else { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index 3e617de8c6..0d48f1532f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -62,6 +62,8 @@ import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import java.util.UUID; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; + @Component @Slf4j @TbCoreComponent @@ -124,7 +126,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { try { Device device = deviceService.findDeviceById(tenantId, deviceId); ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); - TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, device.getCustomerId(), + TbMsg tbMsg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, device.getCustomerId(), getActionTbMsgMetaData(edge, device.getCustomerId()), TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override @@ -138,7 +140,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { } }); } catch (JsonProcessingException | IllegalArgumentException e) { - log.warn("[{}] Failed to push device action to rule engine: {}", deviceId, DataConstants.ENTITY_CREATED, e); + log.warn("[{}] Failed to push device action to rule engine: {}", deviceId, ENTITY_CREATED.name(), e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index fece86b92a..6a7ffe2c40 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -73,6 +73,8 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; + @Slf4j public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { @@ -257,7 +259,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { @Override public void onSuccess(@Nullable Void tmp) { var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), DataConstants.ATTRIBUTES_UPDATED, entityId, + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), ATTRIBUTES_UPDATED.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java index 0a75404fb4..719158403c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java @@ -21,7 +21,6 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; @@ -53,6 +52,8 @@ import org.thingsboard.server.service.gateway_device.GatewayNotificationsService import java.util.List; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED_FROM_TENANT; + @Slf4j @Service @RequiredArgsConstructor @@ -286,7 +287,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { String data = JacksonUtil.toString(JacksonUtil.valueToTree(assignedDevice)); if (data != null) { - TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), + TbMsg tbMsg = TbMsg.newMsg(ENTITY_ASSIGNED_FROM_TENANT.name(), assignedDevice.getId(), assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null); } diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java index bca7c7d81a..2ec8f09bd0 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.rpc; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -49,6 +48,8 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import static org.thingsboard.server.common.data.msg.TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE; + /** * Created by ashvayka on 27.03.18. */ @@ -182,7 +183,7 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { entityNode.put(DataConstants.ADDITIONAL_INFO, msg.getAdditionalInfo()); try { - TbMsg tbMsg = TbMsg.newMsg(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), Optional.ofNullable(currentUser).map(User::getCustomerId).orElse(null), metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); + TbMsg tbMsg = TbMsg.newMsg(RPC_CALL_FROM_SERVER_TO_DEVICE.name(), msg.getDeviceId(), Optional.ofNullable(currentUser).map(User::getCustomerId).orElse(null), metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null); } catch (IllegalArgumentException e) { throw new RuntimeException(e); 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 554359fd6d..965b781fe9 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 @@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityTrigger; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.query.EntityData; @@ -63,7 +64,6 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; -import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityTrigger; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; @@ -102,11 +102,11 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import static org.thingsboard.server.common.data.DataConstants.ACTIVITY_EVENT; -import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT; -import static org.thingsboard.server.common.data.DataConstants.DISCONNECT_EVENT; -import static org.thingsboard.server.common.data.DataConstants.INACTIVITY_EVENT; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; /** * Created by ashvayka on 01.05.18. @@ -229,7 +229,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService NORMAL_NAMES = EnumSet.allOf(EntityType.class).stream() + .map(EntityType::getNormalName).collect(Collectors.toUnmodifiableList()); + @Getter private final String normalName = StringUtils.capitalize(StringUtils.removeStart(name(), "TB_") .toLowerCase().replaceAll("_", " ")); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java index 01009751d4..056be5a958 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java @@ -16,49 +16,61 @@ package org.thingsboard.server.common.data.audit; import lombok.Getter; +import org.thingsboard.server.common.data.msg.TbMsgType; + +import java.util.Optional; -@Getter public enum ActionType { - ADDED(false), // log entity - DELETED(false), // log string id - UPDATED(false), // log entity - ATTRIBUTES_UPDATED(false), // log attributes/values - ATTRIBUTES_DELETED(false), // log attributes - TIMESERIES_UPDATED(false), // log timeseries update - TIMESERIES_DELETED(false), // log timeseries - RPC_CALL(false), // log method and params - CREDENTIALS_UPDATED(false), // log new credentials - ASSIGNED_TO_CUSTOMER(false), // log customer name - UNASSIGNED_FROM_CUSTOMER(false), // log customer name - ACTIVATED(false), // log string id - SUSPENDED(false), // log string id - CREDENTIALS_READ(true), // log device id - ATTRIBUTES_READ(true), // log attributes - RELATION_ADD_OR_UPDATE(false), - RELATION_DELETED(false), - RELATIONS_DELETED(false), - ALARM_ACK(false), - ALARM_CLEAR(false), - ALARM_DELETE(false), - ALARM_ASSIGNED(false), - ALARM_UNASSIGNED(false), - LOGIN(false), - LOGOUT(false), - LOCKOUT(false), - ASSIGNED_FROM_TENANT(false), - ASSIGNED_TO_TENANT(false), - PROVISION_SUCCESS(false), - PROVISION_FAILURE(false), - ASSIGNED_TO_EDGE(false), // log edge name - UNASSIGNED_FROM_EDGE(false), - ADDED_COMMENT(false), - UPDATED_COMMENT(false), - DELETED_COMMENT(false), - SMS_SENT(false); + ADDED(false, TbMsgType.ENTITY_CREATED), // log entity + DELETED(false, TbMsgType.ENTITY_DELETED), // log string id + UPDATED(false, TbMsgType.ENTITY_UPDATED), // log entity + ATTRIBUTES_UPDATED(false, TbMsgType.ATTRIBUTES_UPDATED), // log attributes/values + ATTRIBUTES_DELETED(false, TbMsgType.ATTRIBUTES_DELETED), // log attributes + TIMESERIES_UPDATED(false, TbMsgType.TIMESERIES_UPDATED), // log timeseries update + TIMESERIES_DELETED(false, TbMsgType.TIMESERIES_DELETED), // log timeseries + RPC_CALL(false, null), // log method and params + CREDENTIALS_UPDATED(false, null), // log new credentials + ASSIGNED_TO_CUSTOMER(false, TbMsgType.ENTITY_ASSIGNED), // log customer name + UNASSIGNED_FROM_CUSTOMER(false, TbMsgType.ENTITY_UNASSIGNED), // log customer name + ACTIVATED(false, null), // log string id + SUSPENDED(false, null), // log string id + CREDENTIALS_READ(true, null), // log device id + ATTRIBUTES_READ(true, null), // log attributes + RELATION_ADD_OR_UPDATE(false, TbMsgType.RELATION_ADD_OR_UPDATE), + RELATION_DELETED(false, TbMsgType.RELATION_DELETED), + RELATIONS_DELETED(false, TbMsgType.RELATIONS_DELETED), + ALARM_ACK(false, TbMsgType.ALARM_ACK), + ALARM_CLEAR(false, TbMsgType.ALARM_CLEAR), + ALARM_DELETE(false, TbMsgType.ALARM_DELETE), + ALARM_ASSIGNED(false, TbMsgType.ALARM_ASSIGNED), + ALARM_UNASSIGNED(false, TbMsgType.ALARM_UNASSIGNED), + LOGIN(false, null), + LOGOUT(false, null), + LOCKOUT(false, null), + ASSIGNED_FROM_TENANT(false, TbMsgType.ENTITY_ASSIGNED_FROM_TENANT), + ASSIGNED_TO_TENANT(false, TbMsgType.ENTITY_ASSIGNED_TO_TENANT), + PROVISION_SUCCESS(false, TbMsgType.PROVISION_SUCCESS), + PROVISION_FAILURE(false, TbMsgType.PROVISION_FAILURE), + ASSIGNED_TO_EDGE(false, TbMsgType.ENTITY_ASSIGNED_TO_EDGE), // log edge name + UNASSIGNED_FROM_EDGE(false, TbMsgType.ENTITY_UNASSIGNED_FROM_EDGE), + ADDED_COMMENT(false, TbMsgType.COMMENT_CREATED), + UPDATED_COMMENT(false, TbMsgType.COMMENT_UPDATED), + DELETED_COMMENT(false, null), + SMS_SENT(false, null); + + @Getter private final boolean isRead; - ActionType(boolean isRead) { + private final TbMsgType ruleEngineMsgType; + + ActionType(boolean isRead, TbMsgType ruleEngineMsgType) { this.isRead = isRead; + this.ruleEngineMsgType = ruleEngineMsgType; } + + public Optional getRuleEngineMsgType() { + return Optional.ofNullable(ruleEngineMsgType); + } + } 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 new file mode 100644 index 0000000000..645b8cc9fd --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java @@ -0,0 +1,95 @@ +/** + * 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.common.data.msg; + +import lombok.Getter; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public enum TbMsgType { + + POST_ATTRIBUTES_REQUEST("Post attributes"), + POST_TELEMETRY_REQUEST("Post telemetry"), + TO_SERVER_RPC_REQUEST("RPC Request from Device"), + ACTIVITY_EVENT("Activity Event"), + INACTIVITY_EVENT("Inactivity Event"), + CONNECT_EVENT("Connect Event"), + DISCONNECT_EVENT("Disconnect Event"), + ENTITY_CREATED("Entity Created"), + ENTITY_UPDATED("Entity Updated"), + ENTITY_DELETED("Entity Deleted"), + ENTITY_ASSIGNED("Entity Assigned"), + ENTITY_UNASSIGNED("Entity Unassigned"), + ATTRIBUTES_UPDATED("Attributes Updated"), + ATTRIBUTES_DELETED("Attributes Deleted"), + ALARM(null), + ALARM_ACK("Alarm Acknowledged"), + ALARM_CLEAR("Alarm Cleared"), + ALARM_DELETE("Alarm Deleted"), + ALARM_ASSIGNED("Alarm Assigned"), + ALARM_UNASSIGNED("Alarm Unassigned"), + COMMENT_CREATED("Comment Created"), + COMMENT_UPDATED("Comment Updated"), + RPC_CALL_FROM_SERVER_TO_DEVICE("RPC Request to Device"), + ENTITY_ASSIGNED_FROM_TENANT("Entity Assigned From Tenant"), + ENTITY_ASSIGNED_TO_TENANT("Entity Assigned To Tenant"), + ENTITY_ASSIGNED_TO_EDGE(null), + ENTITY_UNASSIGNED_FROM_EDGE(null), + TIMESERIES_UPDATED("Timeseries Updated"), + TIMESERIES_DELETED("Timeseries Deleted"), + RPC_QUEUED("RPC Queued"), + RPC_SENT("RPC Sent"), + RPC_DELIVERED("RPC Delivered"), + RPC_SUCCESSFUL("RPC Successful"), + RPC_TIMEOUT("RPC Timeout"), + RPC_EXPIRED("RPC Expired"), + RPC_FAILED("RPC Failed"), + RPC_DELETED("RPC Deleted"), + RELATION_ADD_OR_UPDATE("Relation Added or Updated"), + RELATION_DELETED("Relation Deleted"), + RELATIONS_DELETED("All Relations Deleted"), + PROVISION_SUCCESS(null), + PROVISION_FAILURE(null); + + public static final String OTHER = "Other"; + + public static final List NODE_CONNECTIONS = EnumSet.allOf(TbMsgType.class).stream() + .map(TbMsgType::getNodeConnection).filter(Objects::nonNull).collect(Collectors.toUnmodifiableList()); + + @Getter + private final String nodeConnection; + + TbMsgType(String nodeConnection) { + this.nodeConnection = nodeConnection; + } + + public static String getNodeConnection(String msgType) { + if (msgType == null) { + return OTHER; + } else { + return Arrays.stream(TbMsgType.values()) + .filter(type -> type.name().equals(msgType)) + .findFirst() + .map(TbMsgType::getNodeConnection) + .orElse(OTHER); + } + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java index 5c54687205..22ffe34769 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java @@ -24,7 +24,6 @@ public class EmptyNodeConfiguration implements NodeConfiguration { if (alarmResult.alarm == null) { - ctx.tellNext(msg, "False"); + ctx.tellNext(msg, TbNodeConnectionType.FALSE); } else if (alarmResult.isCreated) { - tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_CREATED, "Created"); + tellNext(ctx, msg, alarmResult, ENTITY_CREATED.name(), "Created"); } else if (alarmResult.isUpdated) { - tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_UPDATED, "Updated"); + tellNext(ctx, msg, alarmResult, ENTITY_UPDATED.name(), "Updated"); } else if (alarmResult.isCleared) { - tellNext(ctx, msg, alarmResult, DataConstants.ALARM_CLEAR, "Cleared"); + tellNext(ctx, msg, alarmResult, ALARM_CLEAR.name(), "Cleared"); } else { ctx.tellSuccess(msg); } @@ -96,7 +101,7 @@ public abstract class TbAbstractAlarmNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 6f7bb7cf8d..61004bfa11 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -36,7 +36,6 @@ import org.thingsboard.server.common.data.objects.AttributesEntityView; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.util.CollectionsUtil; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import javax.annotation.Nullable; @@ -45,7 +44,12 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +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.rule.engine.api.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( @@ -71,14 +75,14 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (DataConstants.ATTRIBUTES_UPDATED.equals(msg.getType()) || - DataConstants.ATTRIBUTES_DELETED.equals(msg.getType()) || - DataConstants.ACTIVITY_EVENT.equals(msg.getType()) || - DataConstants.INACTIVITY_EVENT.equals(msg.getType()) || - SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msg.getType())) { + if (ATTRIBUTES_UPDATED.name().equals(msg.getType()) || + ATTRIBUTES_DELETED.name().equals(msg.getType()) || + ACTIVITY_EVENT.name().equals(msg.getType()) || + INACTIVITY_EVENT.name().equals(msg.getType()) || + POST_ATTRIBUTES_REQUEST.name().equals(msg.getType())) { if (!msg.getMetaData().getData().isEmpty()) { long now = System.currentTimeMillis(); - String scope = msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ? + String scope = msg.getType().equals(POST_ATTRIBUTES_REQUEST.name()) ? DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue(DataConstants.SCOPE); ListenableFuture> entityViewsFuture = @@ -90,7 +94,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { long startTime = entityView.getStartTimeMs(); long endTime = entityView.getEndTimeMs(); if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { - if (DataConstants.ATTRIBUTES_DELETED.equals(msg.getType())) { + if (ATTRIBUTES_DELETED.name().equals(msg.getType())) { List attributes = new ArrayList<>(); for (JsonElement element : new JsonParser().parse(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray()) { if (element.isJsonPrimitive()) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index 88f2be46a8..58c42a1109 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -33,7 +33,7 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; +import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 51effe02ef..6bd8f52285 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -41,7 +41,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static org.thingsboard.common.util.DonAsynchron.withCallback; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; +import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index 6890aa6bf4..af119d263f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -24,7 +24,7 @@ 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.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -196,7 +196,7 @@ public class TbMsgDeduplicationNode implements TbNode { private void enqueueForTellNextWithRetry(TbContext ctx, TbMsg msg, int retryAttempt) { if (config.getMaxRetries() > retryAttempt) { - ctx.enqueueForTellNext(msg, TbRelationTypes.SUCCESS, + ctx.enqueueForTellNext(msg, TbNodeConnectionType.SUCCESS, () -> { log.trace("[{}][{}][{}] Successfully enqueue deduplication result message!", ctx.getSelfId(), msg.getOriginator(), retryAttempt); }, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index 1f18aea86e..17224c18b8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -32,7 +32,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; +import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( 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 318b38aa39..bbec000077 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 @@ -30,13 +30,23 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; +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; + @Slf4j public abstract class AbstractTbMsgPushNode implements TbNode { @@ -73,7 +83,7 @@ public abstract class AbstractTbMsgPushNode metadata) { EdgeEventActionType actionType; - if (SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType) - || DataConstants.TIMESERIES_UPDATED.equals(msgType)) { + if (POST_TELEMETRY_REQUEST.name().equals(msgType) + || TIMESERIES_UPDATED.name().equals(msgType)) { actionType = EdgeEventActionType.TIMESERIES_UPDATED; - } else if (DataConstants.ATTRIBUTES_UPDATED.equals(msgType)) { + } else if (ATTRIBUTES_UPDATED.name().equals(msgType)) { actionType = EdgeEventActionType.ATTRIBUTES_UPDATED; - } else if (SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType)) { + } else if (POST_ATTRIBUTES_REQUEST.name().equals(msgType)) { actionType = EdgeEventActionType.POST_ATTRIBUTES; - } else if (DataConstants.ATTRIBUTES_DELETED.equals(msgType)) { + } else if (ATTRIBUTES_DELETED.name().equals(msgType)) { actionType = EdgeEventActionType.ATTRIBUTES_DELETED; - } else if (DataConstants.CONNECT_EVENT.equals(msgType) - || DataConstants.DISCONNECT_EVENT.equals(msgType) - || DataConstants.ACTIVITY_EVENT.equals(msgType) - || DataConstants.INACTIVITY_EVENT.equals(msgType)) { + } else if (CONNECT_EVENT.name().equals(msgType) + || DISCONNECT_EVENT.name().equals(msgType) + || ACTIVITY_EVENT.name().equals(msgType) + || INACTIVITY_EVENT.name().equals(msgType)) { String scope = metadata.get(SCOPE); if ( StringUtils.isEmpty(scope)) { actionType = EdgeEventActionType.TIMESERIES_UPDATED; @@ -177,16 +187,16 @@ public abstract class AbstractTbMsgPushNodeFailure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbAssetTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java index dcaf6698cd..f76366e622 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java @@ -18,7 +18,6 @@ package org.thingsboard.rule.engine.filter; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; @@ -26,26 +25,27 @@ 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.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import javax.annotation.Nullable; -import java.io.IOException; @Slf4j @RuleNode( type = ComponentType.FILTER, - name = "check alarm status", + name = "alarm status filter", configClazz = TbCheckAlarmStatusNodeConfig.class, - relationTypes = {"True", "False"}, + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Checks alarm status.", - nodeDetails = "Checks the alarm status to match one of the specified statuses.", + nodeDetails = "Checks the alarm status to match one of the specified statuses.

" + + "Output connection types: True, False, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckAlarmStatusConfig") public class TbCheckAlarmStatusNode implements TbNode { + private TbCheckAlarmStatusNodeConfig config; @Override @@ -60,33 +60,24 @@ public class TbCheckAlarmStatusNode implements TbNode { ListenableFuture latest = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), alarm.getId()); - Futures.addCallback(latest, new FutureCallback() { + Futures.addCallback(latest, new FutureCallback<>() { @Override public void onSuccess(@Nullable Alarm result) { - if (result != null) { - boolean isPresent = false; - for (AlarmStatus alarmStatus : config.getAlarmStatusList()) { - if (result.getStatus() == alarmStatus) { - isPresent = true; - break; - } - } - if (isPresent) { - ctx.tellNext(msg, "True"); - } else { - ctx.tellNext(msg, "False"); - } - } else { + if (result == null) { ctx.tellFailure(msg, new TbNodeException("No such alarm found.")); + return; } + boolean isPresent = config.getAlarmStatusList().stream() + .anyMatch(alarmStatus -> result.getStatus() == alarmStatus); + ctx.tellNext(msg, isPresent ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); } @Override public void onFailure(Throwable t) { ctx.tellFailure(msg, t); } - }, MoreExecutors.directExecutor()); - } catch (IllegalArgumentException e) { + }, ctx.getDbCallbackExecutor()); + } catch (Exception e) { log.error("Failed to parse alarm: [{}]", msg.getData()); throw new TbNodeException(e); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java index d979eb10ca..4c15d635b8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java @@ -24,12 +24,14 @@ import java.util.List; @Data public class TbCheckAlarmStatusNodeConfig implements NodeConfiguration { + private List alarmStatusList; @Override public TbCheckAlarmStatusNodeConfig defaultConfiguration() { - TbCheckAlarmStatusNodeConfig config = new TbCheckAlarmStatusNodeConfig(); + var config = new TbCheckAlarmStatusNodeConfig(); config.setAlarmStatusList(Arrays.asList(AlarmStatus.ACTIVE_ACK, AlarmStatus.ACTIVE_UNACK)); return config; } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java index 3853b3a62a..c461b258a8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java @@ -22,6 +22,7 @@ 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.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -33,12 +34,12 @@ import java.util.Map; @RuleNode( type = ComponentType.FILTER, name = "check fields presence", - relationTypes = {"True", "False"}, + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, configClazz = TbCheckMessageNodeConfiguration.class, nodeDescription = "Checks the presence of the specified fields in the message and/or metadata.", - nodeDetails = "Checks the presence of the specified fields in the message and/or metadata. " + - "By default, the rule node checks that all specified fields need to be present. " + - "Uncheck the 'Check that all specified fields are present' if the presence of at least one field is sufficient.", + nodeDetails = "By default, the rule node checks that all specified fields are present. " + + "Uncheck the 'Check that all selected fields are present' if the presence of at least one field is sufficient.

" + + "Output connection types: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckMessageConfig") public class TbCheckMessageNode implements TbNode { @@ -60,9 +61,9 @@ public class TbCheckMessageNode implements TbNode { public void onMsg(TbContext ctx, TbMsg msg) { try { if (config.isCheckAllKeys()) { - ctx.tellNext(msg, allKeysData(msg) && allKeysMetadata(msg) ? "True" : "False"); + ctx.tellNext(msg, allKeysData(msg) && allKeysMetadata(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); } else { - ctx.tellNext(msg, atLeastOneData(msg) || atLeastOneMetadata(msg) ? "True" : "False"); + ctx.tellNext(msg, atLeastOneData(msg) || atLeastOneMetadata(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); } } catch (Exception e) { ctx.tellFailure(msg, e); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index 4a0d6b05e7..43bec5a3c1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -24,6 +24,7 @@ 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.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -43,12 +44,14 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; @Slf4j @RuleNode( type = ComponentType.FILTER, - name = "check relation", + name = "check relation presence", configClazz = TbCheckRelationNodeConfiguration.class, - relationTypes = {"True", "False"}, + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Checks the presence of the relation between the originator of the message and other entities.", - nodeDetails = "If 'check relation to specific entity' is selected, one must specify a related entity. " + - "Otherwise, the rule node checks the presence of a relation to any entity that matches the direction and relation type criteria.", + nodeDetails = "If 'check relation to specific entity' is selected, you should specify a related entity. " + + "Otherwise, the rule node checks the presence of a relation to any entity. " + + "In both cases, relation lookup is based on configured direction and type.

" + + "Output connection types: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckRelationConfig") public class TbCheckRelationNode implements TbNode { @@ -67,13 +70,11 @@ public class TbCheckRelationNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { - ListenableFuture checkRelationFuture; - if (config.isCheckForSingleEntity()) { - checkRelationFuture = processSingle(ctx, msg); - } else { - checkRelationFuture = processList(ctx, msg); - } - withCallback(checkRelationFuture, filterResult -> ctx.tellNext(msg, filterResult ? "True" : "False"), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); + ListenableFuture checkRelationFuture = config.isCheckForSingleEntity() ? + processSingle(ctx, msg) : processList(ctx, msg); + withCallback(checkRelationFuture, + filterResult -> ctx.tellNext(msg, filterResult ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE), + t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); } private ListenableFuture processSingle(TbContext ctx, TbMsg msg) { @@ -100,11 +101,7 @@ public class TbCheckRelationNode implements TbNode { } private ListenableFuture isEmptyList(List entityRelations) { - if (entityRelations.isEmpty()) { - return Futures.immediateFuture(false); - } else { - return Futures.immediateFuture(true); - } + return entityRelations.isEmpty() ? Futures.immediateFuture(false) : Futures.immediateFuture(true); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java index 16131ecc05..4409702d9a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java @@ -34,7 +34,8 @@ import org.thingsboard.server.common.data.plugin.ComponentType; relationTypes = {"default"}, configClazz = EmptyNodeConfiguration.class, nodeDescription = "Route incoming messages based on the name of the device profile", - nodeDetails = "Route incoming messages based on the name of the device profile. The device profile name is case-sensitive", + nodeDetails = "Route incoming messages based on the name of the device profile. The device profile name is case-sensitive

" + + "Output connection types: Profile name of message originator or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbDeviceTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index 0bde9402a7..1b8461b44b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -22,6 +22,7 @@ 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.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.script.ScriptLanguage; @@ -32,7 +33,8 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; @Slf4j @RuleNode( type = ComponentType.FILTER, - name = "script", relationTypes = {"True", "False"}, + name = "script", + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, configClazz = TbJsFilterNodeConfiguration.class, nodeDescription = "Filter incoming messages using TBEL or JS script", nodeDetails = "Evaluates boolean function using incoming message. " + @@ -40,7 +42,8 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; "Script function should return boolean value and accepts three parameters:
" + "Message payload can be accessed via msg property. For example msg.temperature < 10;
" + "Message metadata can be accessed via metadata property. For example metadata.customerName === 'John';
" + - "Message type can be accessed via msgType property.", + "Message type can be accessed via msgType property.

" + + "Output connection types: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeScriptConfig" ) @@ -62,7 +65,7 @@ public class TbJsFilterNode implements TbNode { withCallback(scriptEngine.executeFilterAsync(msg), filterResult -> { ctx.logJsEvalResponse(); - ctx.tellNext(msg, filterResult ? "True" : "False"); + ctx.tellNext(msg, filterResult ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); }, t -> { ctx.tellFailure(msg, t); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index 706978846a..8c0f058ed3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -44,7 +44,8 @@ import java.util.Set; "If Array is empty - message not routed to next Node. " + "Message payload can be accessed via msg property. For example msg.temperature < 10;
" + "Message metadata can be accessed via metadata property. For example metadata.customerName === 'John';
" + - "Message type can be accessed via msgType property.", + "Message type can be accessed via msgType property.

" + + "Output connection types: Custom connection(s) defined by switch node or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeSwitchConfig") public class TbJsSwitchNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java index 765deb2ea7..5074991cb1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java @@ -21,6 +21,7 @@ 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.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -33,9 +34,10 @@ import org.thingsboard.server.common.msg.TbMsg; type = ComponentType.FILTER, name = "message type", configClazz = TbMsgTypeFilterNodeConfiguration.class, - relationTypes = {"True", "False"}, + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Filter incoming messages by Message Type", - nodeDetails = "If incoming MessageType is expected - send Message via True chain, otherwise False chain is used.", + nodeDetails = "If incoming message type is expected - send Message via True chain, otherwise False chain is used.

" + + "Output connection types: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeMessageTypeConfig") public class TbMsgTypeFilterNode implements TbNode { @@ -49,7 +51,7 @@ public class TbMsgTypeFilterNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ctx.tellNext(msg, config.getMessageTypes().contains(msg.getType()) ? "True" : "False"); + ctx.tellNext(msg, config.getMessageTypes().contains(msg.getType()) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index 16649f8f85..99a3d41c3a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -19,24 +19,20 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.data.msg.TbMsgType; 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.DataConstants; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; @Slf4j @RuleNode( type = ComponentType.FILTER, name = "message type switch", configClazz = EmptyNodeConfiguration.class, - relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "RPC Queued", "RPC Sent", "RPC Delivered", "RPC Successful", "RPC Timeout", "RPC Expired", "RPC Failed", "RPC Deleted", - "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned", - "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Alarm Assigned", "Alarm Unassigned", "Comment Created", "Comment Updated", "Other", "Entity Assigned From Tenant", "Entity Assigned To Tenant", - "Relation Added or Updated", "Relation Deleted", "All Relations Deleted", "Timeseries Updated", "Timeseries Deleted"}, + relationTypes = {}, // should always be empty. We add the relation types for this node in AnnotationComponentDiscoveryService. nodeDescription = "Route incoming messages by Message Type", nodeDetails = "Sends messages with message types \"Post attributes\", \"Post telemetry\", \"RPC Request\" etc. via corresponding chain, otherwise Other chain is used.", uiResources = {"static/rulenode/rulenode-core-config.js"}, @@ -52,83 +48,7 @@ public class TbMsgTypeSwitchNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - String relationType; - if (msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) { - relationType = "Post attributes"; - } else if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { - relationType = "Post telemetry"; - } else if (msg.getType().equals(SessionMsgType.TO_SERVER_RPC_REQUEST.name())) { - relationType = "RPC Request from Device"; - } else if (msg.getType().equals(DataConstants.ACTIVITY_EVENT)) { - relationType = "Activity Event"; - } else if (msg.getType().equals(DataConstants.INACTIVITY_EVENT)) { - relationType = "Inactivity Event"; - } else if (msg.getType().equals(DataConstants.CONNECT_EVENT)) { - relationType = "Connect Event"; - } else if (msg.getType().equals(DataConstants.DISCONNECT_EVENT)) { - relationType = "Disconnect Event"; - } else if (msg.getType().equals(DataConstants.ENTITY_CREATED)) { - relationType = "Entity Created"; - } else if (msg.getType().equals(DataConstants.ENTITY_UPDATED)) { - relationType = "Entity Updated"; - } else if (msg.getType().equals(DataConstants.ENTITY_DELETED)) { - relationType = "Entity Deleted"; - } else if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED)) { - relationType = "Entity Assigned"; - } else if (msg.getType().equals(DataConstants.ENTITY_UNASSIGNED)) { - relationType = "Entity Unassigned"; - } else if (msg.getType().equals(DataConstants.ATTRIBUTES_UPDATED)) { - relationType = "Attributes Updated"; - } else if (msg.getType().equals(DataConstants.ATTRIBUTES_DELETED)) { - relationType = "Attributes Deleted"; - } else if (msg.getType().equals(DataConstants.ALARM_ACK)) { - relationType = "Alarm Acknowledged"; - } else if (msg.getType().equals(DataConstants.ALARM_CLEAR)) { - relationType = "Alarm Cleared"; - } else if (msg.getType().equals(DataConstants.ALARM_ASSIGNED)) { - relationType = "Alarm Assigned"; - } else if (msg.getType().equals(DataConstants.ALARM_UNASSIGNED)) { - relationType = "Alarm Unassigned"; - } else if (msg.getType().equals(DataConstants.COMMENT_CREATED)) { - relationType = "Comment Created"; - } else if (msg.getType().equals(DataConstants.COMMENT_UPDATED)) { - relationType = "Comment Updated"; - } else if (msg.getType().equals(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE)) { - relationType = "RPC Request to Device"; - } else if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED_FROM_TENANT)) { - relationType = "Entity Assigned From Tenant"; - } else if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED_TO_TENANT)) { - relationType = "Entity Assigned To Tenant"; - } else if (msg.getType().equals(DataConstants.TIMESERIES_UPDATED)) { - relationType = "Timeseries Updated"; - } else if (msg.getType().equals(DataConstants.TIMESERIES_DELETED)) { - relationType = "Timeseries Deleted"; - } else if (msg.getType().equals(DataConstants.RPC_QUEUED)) { - relationType = "RPC Queued"; - } else if (msg.getType().equals(DataConstants.RPC_SENT)) { - relationType = "RPC Sent"; - } else if (msg.getType().equals(DataConstants.RPC_DELIVERED)) { - relationType = "RPC Delivered"; - } else if (msg.getType().equals(DataConstants.RPC_SUCCESSFUL)) { - relationType = "RPC Successful"; - } else if (msg.getType().equals(DataConstants.RPC_TIMEOUT)) { - relationType = "RPC Timeout"; - } else if (msg.getType().equals(DataConstants.RPC_EXPIRED)) { - relationType = "RPC Expired"; - } else if (msg.getType().equals(DataConstants.RPC_FAILED)) { - relationType = "RPC Failed"; - } else if (msg.getType().equals(DataConstants.RPC_DELETED)) { - relationType = "RPC Deleted"; - } else if (msg.getType().equals(DataConstants.RELATION_ADD_OR_UPDATE)) { - relationType = "Relation Added or Updated"; - } else if (msg.getType().equals(DataConstants.RELATION_DELETED)) { - relationType = "Relation Deleted"; - } else if (msg.getType().equals(DataConstants.RELATIONS_DELETED)) { - relationType = "All Relations Deleted"; - } else { - relationType = "Other"; - } - ctx.tellNext(msg, relationType); + ctx.tellNext(msg, TbMsgType.getNodeConnection(msg.getType())); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java index f4e13d761e..8c32d8a1c9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java @@ -21,6 +21,7 @@ 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.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -31,9 +32,10 @@ import org.thingsboard.server.common.msg.TbMsg; type = ComponentType.FILTER, name = "entity type", configClazz = TbOriginatorTypeFilterNodeConfiguration.class, - relationTypes = {"True", "False"}, + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Filter incoming messages by the type of message originator entity", - nodeDetails = "Checks that the entity type of the incoming message originator matches one of the values specified in the filter.", + nodeDetails = "Checks that the entity type of the incoming message originator matches one of the values specified in the filter.

" + + "Output connection types: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeOriginatorTypeConfig") public class TbOriginatorTypeFilterNode implements TbNode { @@ -48,7 +50,7 @@ public class TbOriginatorTypeFilterNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { EntityType originatorType = msg.getOriginator().getEntityType(); - ctx.tellNext(msg, config.getOriginatorTypes().contains(originatorType) ? "True" : "False"); + ctx.tellNext(msg, config.getOriginatorTypes().contains(originatorType) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java index a920af5066..11365a2b1d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java @@ -19,8 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -29,55 +27,17 @@ import org.thingsboard.server.common.data.plugin.ComponentType; type = ComponentType.FILTER, name = "entity type switch", configClazz = EmptyNodeConfiguration.class, - relationTypes = {"Device", "Asset", "Alarm", "Entity View", "Tenant", "Customer", "User", "Dashboard", "Rule chain", "Rule node", "Edge"}, + relationTypes = {}, // should always be empty. We add the relation types for this node in AnnotationComponentDiscoveryService. nodeDescription = "Route incoming messages by Message Originator Type", - nodeDetails = "Routes messages to chain according to the entity type ('Device', 'Asset', etc.).", + nodeDetails = "Routes messages to chain according to the entity type ('Device', 'Asset', etc.).

" + + "Output connection types: entityType of the message originator or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbOriginatorTypeSwitchNode extends TbAbstractTypeSwitchNode { @Override - protected String getRelationType(TbContext ctx, EntityId originator) throws TbNodeException { - String relationType; - EntityType originatorType = originator.getEntityType(); - switch (originatorType) { - case TENANT: - relationType = "Tenant"; - break; - case CUSTOMER: - relationType = "Customer"; - break; - case USER: - relationType = "User"; - break; - case DASHBOARD: - relationType = "Dashboard"; - break; - case ASSET: - relationType = "Asset"; - break; - case DEVICE: - relationType = "Device"; - break; - case ENTITY_VIEW: - relationType = "Entity View"; - break; - case EDGE: - relationType = "Edge"; - break; - case RULE_CHAIN: - relationType = "Rule chain"; - break; - case RULE_NODE: - relationType = "Rule node"; - break; - case ALARM: - relationType = "Alarm"; - break; - default: - throw new TbNodeException("Unsupported originator type: " + originatorType); - } - return relationType; + protected String getRelationType(TbContext ctx, EntityId originator) { + return originator.getEntityType().getNormalName(); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java index 35baaf461b..e0ffc564db 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java @@ -21,7 +21,7 @@ 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.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -48,7 +48,7 @@ public class TbCheckpointNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ctx.enqueueForTellNext(msg, queueName, TbRelationTypes.SUCCESS, () -> ctx.ack(msg), error -> ctx.tellFailure(msg, error)); + ctx.enqueueForTellNext(msg, queueName, TbNodeConnectionType.SUCCESS, () -> ctx.ack(msg), error -> ctx.tellFailure(msg, error)); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java index 373118ebdd..b4d217d799 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java @@ -19,6 +19,7 @@ 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.TbNodeException; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -30,7 +31,7 @@ import org.thingsboard.server.common.msg.TbMsg; type = ComponentType.FILTER, name = "gps geofencing filter", configClazz = TbGpsGeofencingFilterNodeConfiguration.class, - relationTypes = {"True", "False"}, + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Filter incoming messages by GPS based geofencing", nodeDetails = "Extracts latitude and longitude parameters from the incoming message and checks them according to configured perimeter.
" + "Configuration:

" + @@ -57,14 +58,15 @@ import org.thingsboard.server.common.msg.TbMsg; "

" + "{\"latitude\": 48.198618758582384, \"longitude\": 24.65322245153503, \"radius\": 100.0, \"radiusUnit\": \"METER\" }" + "

" + - "Available radius units: METER, KILOMETER, FOOT, MILE, NAUTICAL_MILE;", + "Available radius units: METER, KILOMETER, FOOT, MILE, NAUTICAL_MILE;

" + + "Output connection types: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeGpsGeofencingConfig") public class TbGpsGeofencingFilterNode extends AbstractGeofencingNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { - ctx.tellNext(msg, checkMatches(msg) ? "True" : "False"); + ctx.tellNext(msg, checkMatches(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index de1abea224..7afd61da2d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -31,7 +31,7 @@ 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.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.exception.ThingsboardKafkaClientError; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -165,7 +165,7 @@ public class TbKafkaNode implements TbNode { private void processRecord(TbContext ctx, TbMsg msg, RecordMetadata metadata, Exception e) { if (e == null) { TbMsg next = processResponse(ctx, msg, metadata); - ctx.tellNext(next, TbRelationTypes.SUCCESS); + ctx.tellNext(next, TbNodeConnectionType.SUCCESS); } else { TbMsg next = processException(ctx, msg, e); ctx.tellFailure(next, e); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index 2af4978766..d56b0d6890 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -27,7 +27,6 @@ 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.StringUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -35,7 +34,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; +import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; @Slf4j diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java index 14227cadc4..5707d64071 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java @@ -49,7 +49,7 @@ public abstract class TbAbstractGetEntityDetailsNode detailsList) throws TbNodeException { if (detailsList == null || detailsList.isEmpty()) { - throw new TbNodeException("No entity details selected!"); + throw new TbNodeException("At least one entity detail should be selected!"); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index 31f661417b..4397200fe4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -40,7 +40,6 @@ import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.rule.RuleNodeState; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.dao.sql.query.EntityKeyMapping; @@ -55,6 +54,18 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_ACK; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_CLEAR; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UNASSIGNED; +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; + @Slf4j class DeviceState { @@ -136,24 +147,24 @@ class DeviceState { latestValues = fetchLatestValues(ctx, deviceId); } boolean stateChanged = false; - if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { + if (msg.getType().equals(POST_TELEMETRY_REQUEST.name())) { stateChanged = processTelemetry(ctx, msg); - } else if (msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) { + } else if (msg.getType().equals(POST_ATTRIBUTES_REQUEST.name())) { stateChanged = processAttributesUpdateRequest(ctx, msg); - } else if (msg.getType().equals(DataConstants.ACTIVITY_EVENT) || msg.getType().equals(DataConstants.INACTIVITY_EVENT)) { + } else if (msg.getType().equals(ACTIVITY_EVENT.name()) || msg.getType().equals(INACTIVITY_EVENT.name())) { stateChanged = processDeviceActivityEvent(ctx, msg); - } else if (msg.getType().equals(DataConstants.ATTRIBUTES_UPDATED)) { + } else if (msg.getType().equals(ATTRIBUTES_UPDATED.name())) { stateChanged = processAttributesUpdateNotification(ctx, msg); - } else if (msg.getType().equals(DataConstants.ATTRIBUTES_DELETED)) { + } else if (msg.getType().equals(ATTRIBUTES_DELETED.name())) { stateChanged = processAttributesDeleteNotification(ctx, msg); - } else if (msg.getType().equals(DataConstants.ALARM_CLEAR)) { + } else if (msg.getType().equals(ALARM_CLEAR.name())) { stateChanged = processAlarmClearNotification(ctx, msg); - } else if (msg.getType().equals(DataConstants.ALARM_ACK)) { + } else if (msg.getType().equals(ALARM_ACK.name())) { processAlarmAckNotification(ctx, msg); - } else if (msg.getType().equals(DataConstants.ALARM_DELETE)) { + } else if (msg.getType().equals(ALARM_DELETE.name())) { processAlarmDeleteNotification(ctx, msg); } else { - if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED) || msg.getType().equals(DataConstants.ENTITY_UNASSIGNED)) { + if (msg.getType().equals(ENTITY_ASSIGNED.name()) || msg.getType().equals(ENTITY_UNASSIGNED.name())) { dynamicPredicateValueCtx.resetCustomer(); } ctx.tellSuccess(msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index ba987678f3..a8b3ef4f5f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -26,7 +26,6 @@ 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.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; @@ -46,6 +45,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UPDATED; + @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -123,10 +125,10 @@ public class TbDeviceProfileNode implements TbNode { } else { if (EntityType.DEVICE.equals(originatorType)) { DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); - if (msg.getType().equals(DataConstants.ENTITY_UPDATED)) { + if (msg.getType().equals(ENTITY_UPDATED.name())) { invalidateDeviceProfileCache(deviceId, msg.getData()); ctx.tellSuccess(msg); - } else if (msg.getType().equals(DataConstants.ENTITY_DELETED)) { + } else if (msg.getType().equals(ENTITY_DELETED.name())) { removeDeviceState(deviceId); ctx.tellSuccess(msg); } else { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 6f9541f5b1..c7580fb7d3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -43,7 +43,7 @@ import org.springframework.web.util.UriComponentsBuilder; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.credentials.BasicCredentials; import org.thingsboard.rule.engine.credentials.ClientCredentials; @@ -212,7 +212,7 @@ public class TbHttpClient { ctx.tellSuccess(next); } else { TbMsg next = processFailureResponse(ctx, msg, responseEntity); - ctx.tellNext(next, TbRelationTypes.FAILURE); + ctx.tellNext(next, TbNodeConnectionType.FAILURE); } } }); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java index 79bd6f9bf9..936b192c85 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -27,12 +27,13 @@ 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.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -76,7 +77,7 @@ public class TbSendRPCRequestNode implements TbNode { ctx.tellFailure(msg, new RuntimeException("Params are not present in the message!")); } else { int requestId = json.has("requestId") ? json.get("requestId").getAsInt() : random.nextInt(); - boolean restApiCall = msg.getType().equals(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE); + boolean restApiCall = msg.getType().equals(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.name()); tmp = msg.getMetaData().getValue("oneway"); boolean oneway = !StringUtils.isEmpty(tmp) && Boolean.parseBoolean(tmp); @@ -117,7 +118,7 @@ public class TbSendRPCRequestNode implements TbNode { ctx.getRpcService().sendRpcRequestToDevice(request, ruleEngineDeviceRpcResponse -> { if (ruleEngineDeviceRpcResponse.getError().isEmpty()) { TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), ruleEngineDeviceRpcResponse.getResponse().orElse("{}")); - ctx.enqueueForTellNext(next, TbRelationTypes.SUCCESS); + ctx.enqueueForTellNext(next, TbNodeConnectionType.SUCCESS); } else { TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), wrap("error", ruleEngineDeviceRpcResponse.getError().get().name())); ctx.enqueueForTellFailure(next, ruleEngineDeviceRpcResponse.getError().get().name()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java index 7bf08e8647..d96060ffc2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java @@ -22,7 +22,7 @@ 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.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -75,7 +75,7 @@ public abstract class TbAbstractTransformNode implements TbNode { ctx.tellFailure(msg, e); } }); - msgs.forEach(newMsg -> ctx.enqueueForTellNext(newMsg, TbRelationTypes.SUCCESS, wrapper::onSuccess, wrapper::onFailure)); + msgs.forEach(newMsg -> ctx.enqueueForTellNext(newMsg, TbNodeConnectionType.SUCCESS, wrapper::onSuccess, wrapper::onFailure)); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java index ae7ceade97..9c86891bc0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java @@ -25,7 +25,7 @@ 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.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -79,7 +79,7 @@ public class TbSplitArrayMsgNode implements TbNode { }); data.forEach(msgNode -> { TbMsg outMsg = TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(msgNode)); - ctx.enqueueForTellNext(outMsg, TbRelationTypes.SUCCESS, wrapper::onSuccess, wrapper::onFailure); + ctx.enqueueForTellNext(outMsg, TbNodeConnectionType.SUCCESS, wrapper::onSuccess, wrapper::onFailure); }); } } else { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java index 22df15631e..3b8a8c9193 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java @@ -28,8 +28,8 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; @@ -54,6 +54,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; @RunWith(MockitoJUnitRunner.class) public class TbCreateRelationNodeTest { @@ -111,7 +112,7 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) .thenReturn(Futures.immediateFuture(false)); @@ -119,7 +120,7 @@ public class TbCreateRelationNodeTest { .thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); - verify(ctx).tellNext(msg, TbRelationTypes.SUCCESS); + verify(ctx).tellNext(msg, TbNodeConnectionType.SUCCESS); } @Test @@ -138,7 +139,7 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); EntityRelation relation = new EntityRelation(); when(ctx.getRelationService().findByToAndTypeAsync(any(), eq(msg.getOriginator()), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) @@ -150,7 +151,7 @@ public class TbCreateRelationNodeTest { .thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); - verify(ctx).tellNext(msg, TbRelationTypes.SUCCESS); + verify(ctx).tellNext(msg, TbNodeConnectionType.SUCCESS); } @Test @@ -169,7 +170,7 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) .thenReturn(Futures.immediateFuture(false)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java index 84deafe7e4..98c0c62231 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java @@ -49,10 +49,18 @@ import java.util.UUID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; @RunWith(MockitoJUnitRunner.class) public class TbMsgPushToEdgeNodeTest { + private static final List MISC_EVENTS = List.of(CONNECT_EVENT.name(), DISCONNECT_EVENT.name(), + ACTIVITY_EVENT.name(), INACTIVITY_EVENT.name()); + TbMsgPushToEdgeNode node; private final TenantId tenantId = TenantId.fromUUID(UUID.randomUUID()); @@ -102,7 +110,7 @@ public class TbMsgPushToEdgeNodeTest { PageData edgePageData = new PageData<>(List.of(edgeId), 1, 1, false); Mockito.when(edgeService.findRelatedEdgeIdsByEntityId(tenantId, userId, new PageLink(TbMsgPushToEdgeNode.DEFAULT_PAGE_SIZE))).thenReturn(edgePageData); - TbMsg msg = TbMsg.newMsg(DataConstants.ATTRIBUTES_UPDATED, userId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(ATTRIBUTES_UPDATED.name(), userId, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", null, null); node.onMsg(ctx, msg); @@ -112,9 +120,7 @@ public class TbMsgPushToEdgeNodeTest { @Test public void testMiscEventsProcessedAsAttributesUpdated() { - List miscEvents = List.of(DataConstants.CONNECT_EVENT, DataConstants.DISCONNECT_EVENT, - DataConstants.ACTIVITY_EVENT, DataConstants.INACTIVITY_EVENT); - for (String event : miscEvents) { + for (String event : MISC_EVENTS) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue(DataConstants.SCOPE, DataConstants.SERVER_SCOPE); testEvent(event, metaData, EdgeEventActionType.ATTRIBUTES_UPDATED, "kv"); @@ -123,9 +129,7 @@ public class TbMsgPushToEdgeNodeTest { @Test public void testMiscEventsProcessedAsTimeseriesUpdated() { - List miscEvents = List.of(DataConstants.CONNECT_EVENT, DataConstants.DISCONNECT_EVENT, - DataConstants.ACTIVITY_EVENT, DataConstants.INACTIVITY_EVENT); - for (String event : miscEvents) { + for (String event : MISC_EVENTS) { testEvent(event, new TbMsgMetaData(), EdgeEventActionType.TIMESERIES_UPDATED, "data"); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 4cfe38f3fc..81e4c70ce0 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -27,6 +27,8 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.script.ScriptLanguage; @@ -55,21 +57,21 @@ public class TbJsFilterNodeTest { private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); @Test - public void falseEvaluationDoNotSendMsg() throws TbNodeException, ScriptException { + public void falseEvaluationDoNotSendMsg() throws TbNodeException { initWithScript(); - TbMsg msg = TbMsg.newMsg("USER", null, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(false)); node.onMsg(ctx, msg); verify(ctx).getDbCallbackExecutor(); - verify(ctx).tellNext(msg, "False"); + verify(ctx).tellNext(msg, TbNodeConnectionType.FALSE); } @Test public void exceptionInJsThrowsException() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFailedFuture(new ScriptException("error"))); @@ -81,12 +83,12 @@ public class TbJsFilterNodeTest { public void metadataConditionCanBeTrue() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); verify(ctx).getDbCallbackExecutor(); - verify(ctx).tellNext(msg, "True"); + verify(ctx).tellNext(msg, TbNodeConnectionType.TRUE); } private void initWithScript() throws TbNodeException { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java index d33b2af030..b480719f64 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java @@ -22,7 +22,6 @@ import org.mockito.ArgumentCaptor; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; @@ -64,6 +63,10 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_CLEAR; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class DeviceStateTest { @@ -115,11 +118,11 @@ public class DeviceStateTest { verify(ctx).enqueueForTellNext(resultMsgCaptor.capture(), eq("Alarm Created")); Alarm alarm = JacksonUtil.fromString(resultMsgCaptor.getValue().getData(), Alarm.class); - deviceState.process(ctx, TbMsg.newMsg(DataConstants.ALARM_CLEAR, deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); + deviceState.process(ctx, TbMsg.newMsg(ALARM_CLEAR.name(), deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); reset(ctx); String deletedAttributes = "{ \"attributes\": [ \"other\" ] }"; - deviceState.process(ctx, TbMsg.newMsg(DataConstants.ATTRIBUTES_DELETED, deviceId, new TbMsgMetaData(), deletedAttributes)); + deviceState.process(ctx, TbMsg.newMsg(ATTRIBUTES_DELETED.name(), deviceId, new TbMsgMetaData(), deletedAttributes)); verify(ctx, never()).enqueueForTellNext(any(), anyString()); } @@ -129,7 +132,7 @@ public class DeviceStateTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); DeviceState deviceState = createDeviceState(deviceId, alarmConfig); - TbMsg attributeUpdateMsg = TbMsg.newMsg(SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), + TbMsg attributeUpdateMsg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), deviceId, new TbMsgMetaData(), "{ \"enabled\": false }"); deviceState.process(ctx, attributeUpdateMsg); @@ -137,9 +140,9 @@ public class DeviceStateTest { verify(ctx).enqueueForTellNext(resultMsgCaptor.capture(), eq("Alarm Created")); Alarm alarm = JacksonUtil.fromString(resultMsgCaptor.getValue().getData(), Alarm.class); - deviceState.process(ctx, TbMsg.newMsg(DataConstants.ALARM_CLEAR, deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); + deviceState.process(ctx, TbMsg.newMsg(ALARM_CLEAR.name(), deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); - TbMsg alarmDeleteNotification = TbMsg.newMsg(DataConstants.ALARM_DELETE, deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm)); + TbMsg alarmDeleteNotification = TbMsg.newMsg(ALARM_DELETE.name(), deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm)); assertDoesNotThrow(() -> { deviceState.process(ctx, alarmDeleteNotification); }); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java index 7bfa5c8bfc..f48a9f670c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java @@ -30,7 +30,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.deduplication.DeduplicationStrategy; import org.thingsboard.rule.engine.deduplication.TbMsgDeduplicationNode; import org.thingsboard.rule.engine.deduplication.TbMsgDeduplicationNodeConfiguration; @@ -173,7 +173,7 @@ public class TbMsgDeduplicationNodeTest { verify(ctx, times(msgCount)).ack(any()); verify(ctx, times(1)).tellFailure(eq(msgToReject), any()); verify(node, times(msgCount + wantedNumberOfTellSelfInvocation + 1)).onMsg(eq(ctx), any()); - verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); + verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.SUCCESS), successCaptor.capture(), failureCaptor.capture()); TbMsg firstMsg = inputMsgs.get(0); TbMsg actualMsg = newMsgCaptor.getValue(); @@ -221,7 +221,7 @@ public class TbMsgDeduplicationNodeTest { verify(ctx, times(msgCount)).ack(any()); verify(ctx, times(1)).tellFailure(eq(msgToReject), any()); verify(node, times(msgCount + wantedNumberOfTellSelfInvocation + 1)).onMsg(eq(ctx), any()); - verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); + verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.SUCCESS), successCaptor.capture(), failureCaptor.capture()); TbMsg actualMsg = newMsgCaptor.getValue(); // msg ids should be different because we create new msg before enqueueForTellNext @@ -263,7 +263,7 @@ public class TbMsgDeduplicationNodeTest { verify(ctx, times(msgCount)).ack(any()); verify(node, times(msgCount + wantedNumberOfTellSelfInvocation)).onMsg(eq(ctx), any()); - verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); + verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.SUCCESS), successCaptor.capture(), failureCaptor.capture()); Assertions.assertEquals(1, newMsgCaptor.getAllValues().size()); TbMsg outMessage = newMsgCaptor.getAllValues().get(0); @@ -309,7 +309,7 @@ public class TbMsgDeduplicationNodeTest { verify(ctx, times(msgCount)).ack(any()); verify(node, times(msgCount + wantedNumberOfTellSelfInvocation)).onMsg(eq(ctx), any()); - verify(ctx, times(2)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); + verify(ctx, times(2)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.SUCCESS), successCaptor.capture(), failureCaptor.capture()); List resultMsgs = newMsgCaptor.getAllValues(); Assertions.assertEquals(2, resultMsgs.size()); @@ -363,7 +363,7 @@ public class TbMsgDeduplicationNodeTest { verify(ctx, times(msgCount)).ack(any()); verify(node, times(msgCount + wantedNumberOfTellSelfInvocation)).onMsg(eq(ctx), any()); - verify(ctx, times(2)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); + verify(ctx, times(2)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.SUCCESS), successCaptor.capture(), failureCaptor.capture()); List resultMsgs = newMsgCaptor.getAllValues(); Assertions.assertEquals(2, resultMsgs.size()); From 11cb696d5c8e110c5f77c45a8fa9e558294638c8 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 21 Jun 2023 16:04:23 +0300 Subject: [PATCH 015/168] added conroller method to retrieve list of commands to publish telemetry --- .../server/controller/DeviceController.java | 27 +++++ .../server/dao/device/DeviceService.java | 2 + .../server/dao/device/DeviceServiceImpl.java | 99 +++++++++++++++++++ 3 files changed, 128 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index bb34f6d5b2..36798b5b75 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -73,8 +73,12 @@ import org.thingsboard.server.service.entitiy.device.TbDeviceService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -125,6 +129,8 @@ public class DeviceController extends BaseController { private final TbDeviceService tbDeviceService; + private final SystemSecurityService systemSecurityService; + @ApiOperation(value = "Get Device (getDeviceById)", notes = "Fetch the Device object based on the provided Device Id. " + "If the user has the authority of 'TENANT_ADMIN', the server checks that the device is owned by the same tenant. " + @@ -155,6 +161,27 @@ public class DeviceController extends BaseController { return checkDeviceInfoId(deviceId, Operation.READ); } + @ApiOperation(value = "Get commands to publish device telemetry (getDevicePublishTelemetryCommands)", + notes = "Fetch the list of commands to publish device telemetry based on device profile " + + "If the user has the authority of 'Tenant Administrator', the server checks that the device is owned by the same tenant. " + + "If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/device/info/{deviceId}/commands", method = RequestMethod.GET) + @ResponseBody + public List getDevicePublishTelemetryCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { + checkParameter(DEVICE_ID, strDeviceId); + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); + URI baseUri = new URI(systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request)); + List commands = deviceService.findDevicePublishTelemetryCommands(device); + return commands.stream() + .map(s -> s.replace("$THINGSBOARD_HOST_NAME", baseUri.getHost()) + .replace("$THINGSBOARD_BASE_URL", baseUri.toString())) + .collect(Collectors.toList()); + } + @ApiOperation(value = "Create Or Update Device (saveDevice)", notes = "Create or update the Device. When creating device, platform generates Device Id as " + UUID_WIKI_LINK + "Device credentials are also generated if not provided in the 'accessToken' request parameter. " + diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index a90ea9a572..d8e2a62040 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -43,6 +43,8 @@ public interface DeviceService extends EntityDaoService { DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId); + List findDevicePublishTelemetryCommands(Device device); + Device findDeviceById(TenantId tenantId, DeviceId deviceId); ListenableFuture findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId); 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 46830f8f76..3f52ec5afe 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 @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -38,6 +39,7 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration; @@ -47,6 +49,10 @@ import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; @@ -122,6 +128,67 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDevicePublishTelemetryCommands(Device device) { + DeviceId deviceId = device.getId(); + log.trace("Executing findDevicePublishTelemetryCommands [{}]", deviceId); + validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); + DeviceCredentialsType credentialsType = deviceCredentials.getCredentialsType(); + + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); + + ArrayList commands = new ArrayList<>(); + switch (deviceProfile.getTransportType()) { + case DEFAULT: + switch (credentialsType) { + case ACCESS_TOKEN: + commands.add(getMqttAccessTokenCommand(deviceCredentials) + " -m {temperature:15}"); + commands.add(getHttpAccessTokenCommand(deviceCredentials) + " --data \"{temperature:16}\""); + commands.add("echo -n {temperature:17} | " + getCoapAccessTokenCommand(deviceCredentials) + " -f-"); + break; + case MQTT_BASIC: + commands.add(getMqttBasicPublishCommand(deviceCredentials) + " -m {temperature:18}"); + break; + case X509_CERTIFICATE: + commands.add(getMqttX509Command() + " -m {temperature:19}"); + break; + } + break; + case MQTT: + MqttDeviceProfileTransportConfiguration transportConfiguration = + (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + TransportPayloadType payloadType = transportConfiguration.getTransportPayloadTypeConfiguration().getTransportPayloadType(); + String payload = (payloadType == TransportPayloadType.PROTOBUF) ? " -f protobufFileName" : " -m {temperature:25}"; + switch (credentialsType) { + case ACCESS_TOKEN: + commands.add(getMqttAccessTokenCommand(deviceCredentials) + payload); + break; + case MQTT_BASIC: + commands.add(getMqttBasicPublishCommand(deviceCredentials) + payload); + break; + case X509_CERTIFICATE: + commands.add(getMqttX509Command() + payload); + break; + } + break; + case COAP: + CoapDeviceProfileTransportConfiguration coapTransportConfiguration = + (CoapDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + CoapDeviceTypeConfiguration coapConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); + if (coapConfiguration instanceof DefaultCoapDeviceTypeConfiguration) { + DefaultCoapDeviceTypeConfiguration configuration = + (DefaultCoapDeviceTypeConfiguration) coapTransportConfiguration.getCoapDeviceTypeConfiguration(); + TransportPayloadType transportPayloadType = configuration.getTransportPayloadTypeConfiguration().getTransportPayloadType(); + String payloadExample = (transportPayloadType == TransportPayloadType.PROTOBUF) ? " -t binary -f protobufFileName" : " -t json -f jsonFileName"; + commands.add(getCoapAccessTokenCommand(deviceCredentials) + payloadExample); + } + break; + } + return commands; + } + @Override public Device findDeviceById(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceById [{}]", deviceId); @@ -681,4 +748,36 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Wed, 21 Jun 2023 18:37:27 +0300 Subject: [PATCH 016/168] refactoring --- .../server/controller/DeviceController.java | 10 ++-- .../server/dao/device/DeviceService.java | 3 +- .../server/dao/device/DeviceServiceImpl.java | 46 ++++++++++--------- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 36798b5b75..6d027c5c5d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -77,7 +77,6 @@ import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; -import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; @@ -174,12 +173,9 @@ public class DeviceController extends BaseController { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); - URI baseUri = new URI(systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request)); - List commands = deviceService.findDevicePublishTelemetryCommands(device); - return commands.stream() - .map(s -> s.replace("$THINGSBOARD_HOST_NAME", baseUri.getHost()) - .replace("$THINGSBOARD_BASE_URL", baseUri.toString())) - .collect(Collectors.toList()); + + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); + return deviceService.findDevicePublishTelemetryCommands(baseUrl, device); } @ApiOperation(value = "Create Or Update Device (saveDevice)", diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index d8e2a62040..79f4781936 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.entity.EntityDaoService; +import java.net.URISyntaxException; import java.util.List; import java.util.UUID; @@ -43,7 +44,7 @@ public interface DeviceService extends EntityDaoService { DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId); - List findDevicePublishTelemetryCommands(Device device); + List findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException; Device findDeviceById(TenantId tenantId, DeviceId deviceId); 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 3f52ec5afe..85aab629d8 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 @@ -19,7 +19,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -80,6 +79,8 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -129,11 +130,12 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDevicePublishTelemetryCommands(Device device) { + public List findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException { DeviceId deviceId = device.getId(); log.trace("Executing findDevicePublishTelemetryCommands [{}]", deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); + String hostname = new URI(baseUrl).getHost(); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); DeviceCredentialsType credentialsType = deviceCredentials.getCredentialsType(); @@ -144,15 +146,15 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Thu, 22 Jun 2023 15:27:45 +0300 Subject: [PATCH 017/168] added tests --- .../server/controller/DeviceController.java | 2 +- .../controller/DeviceControllerTest.java | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 6d027c5c5d..e473163642 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -166,7 +166,7 @@ public class DeviceController extends BaseController { "If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/device/info/{deviceId}/commands", method = RequestMethod.GET) + @RequestMapping(value = "/device/{deviceId}/commands", method = RequestMethod.GET) @ResponseBody public List getDevicePublishTelemetryCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 11d11eccdd..00ccdb1119 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -40,6 +40,8 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackageInfo; @@ -49,6 +51,10 @@ 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.device.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceCredentialsId; @@ -643,6 +649,59 @@ public class DeviceControllerTest extends AbstractControllerTest { Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); } + @Test + public void testFetchPublishTelemetryCommandsForDefaultDevice() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setType("default"); + Device savedDevice = doPost("/api/device", device, Device.class); + List commands = + doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); + + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + assertThat(commands).hasSize(3); + assertThat(commands).containsExactly(String.format("mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -u %s -m \"{temperature:15}\"", + credentials.getCredentialsId()), + String.format("curl -v -X POST http://localhost:80/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:16}\"", + credentials.getCredentialsId()), + String.format("echo -n \"{temperature:17}\" | coap-client -m post coap://localhost:5683/api/v1/%s/telemetry -f-", + credentials.getCredentialsId())); + } + + @Test + public void testFetchPublishTelemetryCommandsForMqttDevice() throws Exception { + DeviceProfile mqttProfile = new DeviceProfile(); + mqttProfile.setName("Mqtt device profile"); + mqttProfile.setType(DeviceProfileType.DEFAULT); + mqttProfile.setTransportType(DeviceTransportType.MQTT); + + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); + deviceProfileData.setTransportConfiguration(new MqttDeviceProfileTransportConfiguration()); + + mqttProfile.setProfileData(deviceProfileData); + mqttProfile.setDefault(false); + mqttProfile.setDefaultRuleChainId(null); + + mqttProfile = doPost("/api/deviceProfile", mqttProfile, DeviceProfile.class); + + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(mqttProfile.getId()); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + List commands = + doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); + assertThat(commands).hasSize(1); + assertThat(commands.get(0)).isEqualTo("mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -u " + + credentials.getCredentialsId() + " -m \"{temperature:25}\""); + } + @Test public void testSaveDeviceCredentials() throws Exception { Device device = new Device(); From a3c085027758c0fa4850dff2f44a0e96e1ed1567 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 23 Jun 2023 19:00:25 +0300 Subject: [PATCH 018/168] added tests for TbCheckAlarmStatusNode && TbMsgTypeFilterNodeTest && TbOriginatorTypeFilterNodeTest && replaced SessionMsgType && refactoring --- .../actors/ruleChain/DefaultTbContext.java | 2 +- .../RuleChainActorMessageProcessor.java | 2 +- .../AnnotationComponentDiscoveryService.java | 4 +- .../processor/device/DeviceEdgeProcessor.java | 4 +- .../telemetry/BaseTelemetryProcessor.java | 7 +- .../update/DefaultDataUpdateService.java | 3 +- ...AbstractRuleEngineFlowIntegrationTest.java | 5 +- .../RuleChainMsgConstructorTest.java | 126 ++++++------- .../script/NashornJsInvokeServiceTest.java | 3 +- .../service/script/TbelInvokeServiceTest.java | 3 +- .../SequentialTimeseriesPersistenceTest.java | 4 +- .../sync/ie/BaseExportImportServiceTest.java | 5 +- .../server/common/data/msg/TbMsgType.java | 18 +- .../data/msg}/TbNodeConnectionType.java | 4 +- .../common/msg/session/SessionMsgType.java | 48 ----- .../transport/coap/CoapSessionMsgType.java | 31 ++++ .../transport/coap/CoapTransportResource.java | 27 ++- .../coap/client/CoapClientContext.java | 4 +- .../coap/client/DefaultCoapClientContext.java | 4 +- .../service/DefaultTransportService.java | 14 +- .../server/dao/sql/event/JpaBaseEventDao.java | 5 +- .../thingsboard/rule/engine/api/RuleNode.java | 3 +- .../engine/action/TbAbstractAlarmNode.java | 2 +- .../action/TbAbstractRelationActionNode.java | 4 +- .../TbCopyAttributesToEntityViewNode.java | 2 +- .../rule/engine/action/TbMsgCountNode.java | 6 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 2 +- .../deduplication/TbMsgDeduplicationNode.java | 2 +- .../rule/engine/delay/TbMsgDelayNode.java | 2 +- .../engine/filter/TbAssetTypeSwitchNode.java | 9 +- .../engine/filter/TbCheckAlarmStatusNode.java | 4 +- .../engine/filter/TbCheckMessageNode.java | 13 +- .../TbCheckMessageNodeConfiguration.java | 2 +- .../engine/filter/TbCheckRelationNode.java | 16 +- .../TbCheckRelationNodeConfiguration.java | 3 +- .../engine/filter/TbDeviceTypeSwitchNode.java | 5 +- .../rule/engine/filter/TbJsFilterNode.java | 4 +- .../rule/engine/filter/TbJsSwitchNode.java | 2 +- .../engine/filter/TbMsgTypeFilterNode.java | 6 +- .../TbMsgTypeFilterNodeConfiguration.java | 13 +- .../engine/filter/TbMsgTypeSwitchNode.java | 6 +- .../filter/TbOriginatorTypeFilterNode.java | 6 +- .../filter/TbOriginatorTypeSwitchNode.java | 2 +- .../rule/engine/flow/TbCheckpointNode.java | 2 +- .../engine/geo/TbGpsGeofencingFilterNode.java | 4 +- .../rule/engine/kafka/TbKafkaNode.java | 2 +- .../rule/engine/mail/TbMsgToEmailNode.java | 2 +- .../engine/metadata/CalculateDeltaNode.java | 11 +- .../rule/engine/rest/TbHttpClient.java | 2 +- .../rule/engine/rpc/TbSendRPCRequestNode.java | 2 +- .../engine/telemetry/TbMsgAttributesNode.java | 4 +- .../engine/telemetry/TbMsgTimeseriesNode.java | 5 +- .../transform/TbAbstractTransformNode.java | 2 +- .../engine/transform/TbSplitArrayMsgNode.java | 2 +- .../rule/engine/TestDbCallbackExecutor.java | 40 +++++ .../rule/engine/action/TbAlarmNodeTest.java | 19 +- .../action/TbCreateRelationNodeTest.java | 20 +-- .../engine/edge/TbMsgPushToEdgeNodeTest.java | 4 +- .../filter/TbAssetTypeSwitchNodeTest.java | 3 +- .../filter/TbCheckAlarmStatusNodeTest.java | 167 ++++++++++++++++++ .../filter/TbDeviceTypeSwitchNodeTest.java | 3 +- .../engine/filter/TbJsFilterNodeTest.java | 2 +- .../filter/TbMsgTypeFilterNodeTest.java | 103 +++++++++++ .../TbOriginatorTypeFilterNodeTest.java | 102 +++++++++++ .../metadata/CalculateDeltaNodeTest.java | 55 +++--- .../TbFetchDeviceCredentialsNodeTest.java | 3 +- .../TbGetCustomerAttributeNodeTest.java | 27 +-- .../TbGetCustomerDetailsNodeTest.java | 23 +-- .../TbGetOriginatorFieldsNodeTest.java | 30 +--- .../TbGetRelatedAttributeNodeTest.java | 26 +-- .../TbGetTenantAttributeNodeTest.java | 26 +-- .../metadata/TbGetTenantDetailsNodeTest.java | 3 +- .../rule/engine/profile/DeviceStateTest.java | 3 +- .../profile/TbDeviceProfileNodeTest.java | 48 ++--- .../engine/rpc/TbSendRPCReplyNodeTest.java | 6 +- .../TbMsgDeleteAttributesNodeTest.java | 3 +- .../transform/TbChangeOriginatorNodeTest.java | 19 +- .../engine/transform/TbCopyKeysNodeTest.java | 3 +- .../transform/TbDeleteKeysNodeTest.java | 3 +- .../engine/transform/TbJsonPathNodeTest.java | 3 +- .../transform/TbMsgDeduplicationNodeTest.java | 11 +- .../transform/TbRenameKeysNodeTest.java | 3 +- .../transform/TbSplitArrayMsgNodeTest.java | 3 +- .../EntitiesCustomerIdAsyncLoaderTest.java | 20 +-- .../util/EntitiesFieldsAsyncLoaderTest.java | 20 +-- ...ntitiesRelatedDeviceIdAsyncLoaderTest.java | 20 +-- ...ntitiesRelatedEntityIdAsyncLoaderTest.java | 19 +- 87 files changed, 769 insertions(+), 546 deletions(-) rename {rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api => common/data/src/main/java/org/thingsboard/server/common/data/msg}/TbNodeConnectionType.java (90%) delete mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java create mode 100644 common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapSessionMsgType.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/TestDbCallbackExecutor.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java 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 778a3db51f..2a15def2f7 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 @@ -33,7 +33,7 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.util.TenantIdLoader; 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 351a012d73..3c8ddb2a7b 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,7 @@ package org.thingsboard.server.actors.ruleChain; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +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; diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index e8fff7ba60..112c406d19 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -31,7 +31,7 @@ import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeDefinition; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.server.common.data.msg.TbMsgType; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbVersionedNode; import org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode; import org.thingsboard.rule.engine.filter.TbOriginatorTypeSwitchNode; @@ -253,7 +253,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe } if (TbMsgTypeSwitchNode.class.equals(clazz)) { relationTypes.addAll(TbMsgType.NODE_CONNECTIONS); - relationTypes.add(TbMsgType.OTHER); + relationTypes.add(TbNodeConnectionType.OTHER); } if (!relationTypes.contains(TbNodeConnectionType.FAILURE)) { relationTypes.add(TbNodeConnectionType.FAILURE); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index 0d48f1532f..3848828522 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -38,6 +38,7 @@ 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.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rpc.RpcError; @@ -46,7 +47,6 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; @@ -218,7 +218,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { ObjectNode data = JacksonUtil.newObjectNode(); data.put("method", deviceRpcCallMsg.getRequestMsg().getMethod()); data.put("params", deviceRpcCallMsg.getRequestMsg().getParams()); - TbMsg tbMsg = TbMsg.newMsg(SessionMsgType.TO_SERVER_RPC_REQUEST.name(), deviceId, null, metaData, + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST.name(), deviceId, null, metaData, TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(data)); tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index 6a7ffe2c40..c55a931194 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -54,7 +54,6 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.model.ModelConstants; @@ -74,6 +73,8 @@ import java.util.List; import java.util.UUID; import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @Slf4j public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { @@ -186,7 +187,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); metaData.putValue("ts", tsKv.getTs() + ""); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), POST_TELEMETRY_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { @@ -230,7 +231,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { SettableFuture futureToSet = SettableFuture.create(); JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), POST_ATTRIBUTES_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index f832b04c21..4135c67a13 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageLink; @@ -497,7 +498,7 @@ public class DefaultDataUpdateService implements DataUpdateService { md.getNodes().add(ruleNode); md.setFirstNodeIndex(newIdx); - md.addConnectionInfo(newIdx, oldIdx, "Success"); + md.addConnectionInfo(newIdx, oldIdx, TbNodeConnectionType.SUCCESS); ruleChainService.saveRuleChainMetaData(tenant.getId(), md, Function.identity()); } } catch (Exception e) { diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java index 6c2e5e9c5e..c448c4220f 100644 --- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.event.Event; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChain; @@ -159,7 +160,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2)); metaData.setFirstNodeIndex(0); - metaData.addConnectionInfo(0, 1, "Success"); + metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS); metaData = saveRuleChainMetaData(metaData); Assert.assertNotNull(metaData); @@ -265,7 +266,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule NodeConnectionInfo connection = new NodeConnectionInfo(); connection.setFromIndex(0); connection.setToIndex(1); - connection.setType("Success"); + connection.setType(TbNodeConnectionType.SUCCESS); rootMetaData.setConnections(Collections.singletonList(connection)); rootMetaData = saveRuleChainMetaData(rootMetaData); Assert.assertNotNull(rootMetaData); 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 dc3cc84c72..c1f34217ac 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 @@ -24,9 +24,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; 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.TbMsgType; import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; @@ -45,6 +47,8 @@ import java.util.UUID; @RunWith(MockitoJUnitRunner.class) public class RuleChainMsgConstructorTest { + private static final String RPC_CONNECTION_TYPE = "RPC"; + private RuleChainMsgConstructor constructor; private TenantId tenantId; @@ -99,19 +103,19 @@ public class RuleChainMsgConstructorTest { Assert.assertEquals("Connections count incorrect!", 13, ruleChainMetadataUpdateMsg.getConnectionsCount()); Assert.assertEquals("Rule chain connections count incorrect!", 0, ruleChainMetadataUpdateMsg.getRuleChainConnectionsCount()); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 6, "Success"), ruleChainMetadataUpdateMsg.getConnections(0)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 10, "Success"), ruleChainMetadataUpdateMsg.getConnections(1)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 0, "Success"), ruleChainMetadataUpdateMsg.getConnections(2)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 11, "Success"), ruleChainMetadataUpdateMsg.getConnections(3)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 11, "Success"), ruleChainMetadataUpdateMsg.getConnections(4)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 11, "Attributes Updated"), ruleChainMetadataUpdateMsg.getConnections(5)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 7, "RPC Request from Device"), ruleChainMetadataUpdateMsg.getConnections(6)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 4, "Post telemetry"), ruleChainMetadataUpdateMsg.getConnections(7)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 5, "Post attributes"), ruleChainMetadataUpdateMsg.getConnections(8)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 8, "Other"), ruleChainMetadataUpdateMsg.getConnections(9)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 9, "RPC Request to Device"), ruleChainMetadataUpdateMsg.getConnections(10)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(7, 11, "Success"), ruleChainMetadataUpdateMsg.getConnections(11)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(10, 9, "RPC"), ruleChainMetadataUpdateMsg.getConnections(12)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 6, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(0)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 10, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(1)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 0, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(2)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 11, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(3)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 11, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(4)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 11, TbMsgType.ATTRIBUTES_UPDATED.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(5)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 7, TbMsgType.TO_SERVER_RPC_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(6)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 4, TbMsgType.POST_TELEMETRY_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(7)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 5, TbMsgType.POST_ATTRIBUTES_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(8)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 8, TbNodeConnectionType.OTHER), ruleChainMetadataUpdateMsg.getConnections(9)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 9, TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(10)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(7, 11, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(11)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(10, 9, RPC_CONNECTION_TYPE), ruleChainMetadataUpdateMsg.getConnections(12)); } @Test @@ -130,20 +134,20 @@ public class RuleChainMsgConstructorTest { Assert.assertEquals("Connections count incorrect!", 10, ruleChainMetadataUpdateMsg.getConnectionsCount()); Assert.assertEquals("Rule chain connections count incorrect!", 1, ruleChainMetadataUpdateMsg.getRuleChainConnectionsCount()); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(2, 5, "Success"), ruleChainMetadataUpdateMsg.getConnections(0)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 9, "Success"), ruleChainMetadataUpdateMsg.getConnections(1)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 9, "Success"), ruleChainMetadataUpdateMsg.getConnections(2)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 9, "Attributes Updated"), ruleChainMetadataUpdateMsg.getConnections(3)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 6, "RPC Request from Device"), ruleChainMetadataUpdateMsg.getConnections(4)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 3, "Post telemetry"), ruleChainMetadataUpdateMsg.getConnections(5)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 4, "Post attributes"), ruleChainMetadataUpdateMsg.getConnections(6)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 7, "Other"), ruleChainMetadataUpdateMsg.getConnections(7)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 8, "RPC Request to Device"), ruleChainMetadataUpdateMsg.getConnections(8)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 9, "Success"), ruleChainMetadataUpdateMsg.getConnections(9)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(2, 5, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(0)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 9, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(1)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 9, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(2)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 9, TbMsgType.ATTRIBUTES_UPDATED.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(3)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 6, TbMsgType.TO_SERVER_RPC_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(4)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 3, TbMsgType.POST_TELEMETRY_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(5)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 4, TbMsgType.POST_ATTRIBUTES_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(6)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 7, TbNodeConnectionType.OTHER), ruleChainMetadataUpdateMsg.getConnections(7)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 8, TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(8)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 9, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(9)); RuleChainConnectionInfoProto ruleChainConnection = ruleChainMetadataUpdateMsg.getRuleChainConnections(0); Assert.assertEquals("From index incorrect!", 2, ruleChainConnection.getFromIndex()); - Assert.assertEquals("Type index incorrect!", "Success", ruleChainConnection.getType()); + Assert.assertEquals("Type index incorrect!", TbNodeConnectionType.SUCCESS, ruleChainConnection.getType()); Assert.assertEquals("Additional info incorrect!", "{\"description\":\"\",\"layoutX\":477,\"layoutY\":560,\"ruleChainNodeId\":\"rule-chain-node-UNDEFINED\"}", ruleChainConnection.getAdditionalInfo()); @@ -172,20 +176,20 @@ public class RuleChainMsgConstructorTest { Assert.assertEquals("Connections count incorrect!", 10, ruleChainMetadataUpdateMsg.getConnectionsCount()); Assert.assertEquals("Rule chain connections count incorrect!", 1, ruleChainMetadataUpdateMsg.getRuleChainConnectionsCount()); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 0, "Success"), ruleChainMetadataUpdateMsg.getConnections(0)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 0, "Attributes Updated"), ruleChainMetadataUpdateMsg.getConnections(1)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 3, "RPC Request from Device"), ruleChainMetadataUpdateMsg.getConnections(2)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 6, "Post telemetry"), ruleChainMetadataUpdateMsg.getConnections(3)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 5, "Post attributes"), ruleChainMetadataUpdateMsg.getConnections(4)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 2, "Other"), ruleChainMetadataUpdateMsg.getConnections(5)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 1, "RPC Request to Device"), ruleChainMetadataUpdateMsg.getConnections(6)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 0, "Success"), ruleChainMetadataUpdateMsg.getConnections(7)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 0, "Success"), ruleChainMetadataUpdateMsg.getConnections(8)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(7, 4, "Success"), ruleChainMetadataUpdateMsg.getConnections(9)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 0, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(0)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 0, TbMsgType.ATTRIBUTES_UPDATED.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(1)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 3, TbMsgType.TO_SERVER_RPC_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(2)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 6, TbMsgType.POST_TELEMETRY_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(3)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 5, TbMsgType.POST_ATTRIBUTES_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(4)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 2, TbNodeConnectionType.OTHER), ruleChainMetadataUpdateMsg.getConnections(5)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 1, TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(6)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 0, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(7)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 0, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(8)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(7, 4, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(9)); RuleChainConnectionInfoProto ruleChainConnection = ruleChainMetadataUpdateMsg.getRuleChainConnections(0); Assert.assertEquals("From index incorrect!", 7, ruleChainConnection.getFromIndex()); - Assert.assertEquals("Type index incorrect!", "Success", ruleChainConnection.getType()); + Assert.assertEquals("Type index incorrect!", TbNodeConnectionType.SUCCESS, ruleChainConnection.getType()); Assert.assertEquals("Additional info incorrect!", "{\"description\":\"\",\"layoutX\":477,\"layoutY\":560,\"ruleChainNodeId\":\"rule-chain-node-UNDEFINED\"}", ruleChainConnection.getAdditionalInfo()); @@ -225,19 +229,19 @@ public class RuleChainMsgConstructorTest { private List createConnections() { List result = new ArrayList<>(); - result.add(createNodeConnectionInfo(3, 6, "Success")); - result.add(createNodeConnectionInfo(3, 10, "Success")); - result.add(createNodeConnectionInfo(3, 0, "Success")); - result.add(createNodeConnectionInfo(4, 11, "Success")); - result.add(createNodeConnectionInfo(5, 11, "Success")); - result.add(createNodeConnectionInfo(6, 11, "Attributes Updated")); - result.add(createNodeConnectionInfo(6, 7, "RPC Request from Device")); - result.add(createNodeConnectionInfo(6, 4, "Post telemetry")); - result.add(createNodeConnectionInfo(6, 5, "Post attributes")); - result.add(createNodeConnectionInfo(6, 8, "Other")); - result.add(createNodeConnectionInfo(6, 9, "RPC Request to Device")); - result.add(createNodeConnectionInfo(7, 11, "Success")); - result.add(createNodeConnectionInfo(10, 9, "RPC")); + result.add(createNodeConnectionInfo(3, 6, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(3, 10, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(3, 0, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(4, 11, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(5, 11, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(6, 11, TbMsgType.ATTRIBUTES_UPDATED.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(6, 7, TbMsgType.TO_SERVER_RPC_REQUEST.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(6, 4, TbMsgType.POST_TELEMETRY_REQUEST.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(6, 5, TbMsgType.POST_ATTRIBUTES_REQUEST.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(6, 8, TbNodeConnectionType.OTHER)); + result.add(createNodeConnectionInfo(6, 9, TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(7, 11, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(10, 9, RPC_CONNECTION_TYPE)); return result; } @@ -280,19 +284,19 @@ public class RuleChainMsgConstructorTest { private List createConnectionsInDifferentOrder() { List result = new ArrayList<>(); - result.add(createNodeConnectionInfo(0, 2, "RPC")); - result.add(createNodeConnectionInfo(4, 1, "Success")); - result.add(createNodeConnectionInfo(5, 1, "Attributes Updated")); - result.add(createNodeConnectionInfo(5, 4, "RPC Request from Device")); - result.add(createNodeConnectionInfo(5, 7, "Post telemetry")); - result.add(createNodeConnectionInfo(5, 6, "Post attributes")); - result.add(createNodeConnectionInfo(5, 3, "Other")); - result.add(createNodeConnectionInfo(5, 2, "RPC Request to Device")); - result.add(createNodeConnectionInfo(6, 1, "Success")); - result.add(createNodeConnectionInfo(7, 1, "Success")); - result.add(createNodeConnectionInfo(8, 11, "Success")); - result.add(createNodeConnectionInfo(8, 5, "Success")); - result.add(createNodeConnectionInfo(8, 0, "Success")); + result.add(createNodeConnectionInfo(0, 2, RPC_CONNECTION_TYPE)); + result.add(createNodeConnectionInfo(4, 1, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(5, 1, TbMsgType.ATTRIBUTES_UPDATED.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(5, 4, TbMsgType.TO_SERVER_RPC_REQUEST.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(5, 7, TbMsgType.POST_TELEMETRY_REQUEST.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(5, 6, TbMsgType.POST_ATTRIBUTES_REQUEST.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(5, 3, TbNodeConnectionType.OTHER)); + result.add(createNodeConnectionInfo(5, 2, TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(6, 1, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(7, 1, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(8, 11, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(8, 5, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(8, 0, TbNodeConnectionType.SUCCESS)); return result; } diff --git a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java index 4373a4c108..8d7de23303 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java @@ -33,6 +33,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @DaoSqlTest @TestPropertySource(properties = { @@ -121,7 +122,7 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest { } private String invokeScript(UUID scriptId, String msg) throws ExecutionException, InterruptedException { - return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", "POST_TELEMETRY_REQUEST").get().toString(); + return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.getRuleNodeConnection()).get().toString(); } } diff --git a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java index ca5dbef5f1..2895c97c90 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java @@ -42,6 +42,7 @@ import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @DaoSqlTest @TestPropertySource(properties = { @@ -216,7 +217,7 @@ class TbelInvokeServiceTest extends AbstractControllerTest { private String invokeScript(UUID scriptId, String str) throws ExecutionException, InterruptedException { var msg = JacksonUtil.fromString(str, Map.class); - return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", "POST_TELEMETRY_REQUEST").get().toString(); + return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.getRuleNodeConnection()).get().toString(); } } diff --git a/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java b/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java index e70a888194..6c060feb14 100644 --- a/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java @@ -37,7 +37,6 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -51,6 +50,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @DaoSqlTest public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest { @@ -133,7 +133,7 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest void saveLatestTsForAssetAndDevice(List devices, Asset asset, int idx) throws ExecutionException, InterruptedException, TimeoutException { for (Device device : devices) { - TbMsg tbMsg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), + TbMsg tbMsg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), device.getId(), getTbMsgMetadata(device.getName(), ts.get(idx)), TbMsgDataType.JSON, diff --git a/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java b/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java index 39b61c6c47..b9b023e9e9 100644 --- a/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java @@ -52,6 +52,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -343,7 +344,7 @@ public abstract class BaseExportImportServiceTest extends AbstractControllerTest metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2)); metaData.setFirstNodeIndex(0); - metaData.addConnectionInfo(0, 1, "Success"); + metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS); ruleChainService.saveRuleChainMetaData(tenantId, metaData, Function.identity()); return ruleChainService.findRuleChainById(tenantId, ruleChain.getId()); @@ -381,7 +382,7 @@ public abstract class BaseExportImportServiceTest extends AbstractControllerTest metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2)); metaData.setFirstNodeIndex(0); - metaData.addConnectionInfo(0, 1, "Success"); + metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS); ruleChainService.saveRuleChainMetaData(tenantId, metaData, Function.identity()); return ruleChainService.findRuleChainById(tenantId, ruleChain.getId()); 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 645b8cc9fd..a974d8adbd 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 @@ -68,27 +68,25 @@ public enum TbMsgType { PROVISION_SUCCESS(null), PROVISION_FAILURE(null); - public static final String OTHER = "Other"; - public static final List NODE_CONNECTIONS = EnumSet.allOf(TbMsgType.class).stream() - .map(TbMsgType::getNodeConnection).filter(Objects::nonNull).collect(Collectors.toUnmodifiableList()); + .map(TbMsgType::getRuleNodeConnection).filter(Objects::nonNull).collect(Collectors.toUnmodifiableList()); @Getter - private final String nodeConnection; + private final String ruleNodeConnection; - TbMsgType(String nodeConnection) { - this.nodeConnection = nodeConnection; + TbMsgType(String ruleNodeConnection) { + this.ruleNodeConnection = ruleNodeConnection; } - public static String getNodeConnection(String msgType) { + public static String getRuleNodeConnection(String msgType) { if (msgType == null) { - return OTHER; + return TbNodeConnectionType.OTHER; } else { return Arrays.stream(TbMsgType.values()) .filter(type -> type.name().equals(msgType)) .findFirst() - .map(TbMsgType::getNodeConnection) - .orElse(OTHER); + .map(TbMsgType::getRuleNodeConnection) + .orElse(TbNodeConnectionType.OTHER); } } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConnectionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbNodeConnectionType.java similarity index 90% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConnectionType.java rename to common/data/src/main/java/org/thingsboard/server/common/data/msg/TbNodeConnectionType.java index e19ac3f6af..584393bf53 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConnectionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbNodeConnectionType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api; +package org.thingsboard.server.common.data.msg; /** * Created by ashvayka on 19.01.18. @@ -26,4 +26,6 @@ public final class TbNodeConnectionType { public static final String TRUE = "True"; public static final String FALSE = "False"; + public static final String OTHER = "Other"; + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java deleted file mode 100644 index dfcf178473..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * 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.common.msg.session; - -public enum SessionMsgType { - GET_ATTRIBUTES_REQUEST(true), POST_ATTRIBUTES_REQUEST(true), GET_ATTRIBUTES_RESPONSE, - SUBSCRIBE_ATTRIBUTES_REQUEST, UNSUBSCRIBE_ATTRIBUTES_REQUEST, ATTRIBUTES_UPDATE_NOTIFICATION, - - POST_TELEMETRY_REQUEST(true), STATUS_CODE_RESPONSE, - - SUBSCRIBE_RPC_COMMANDS_REQUEST, UNSUBSCRIBE_RPC_COMMANDS_REQUEST, - TO_DEVICE_RPC_REQUEST, TO_DEVICE_RPC_RESPONSE, TO_DEVICE_RPC_RESPONSE_ACK, - - TO_SERVER_RPC_REQUEST(true), TO_SERVER_RPC_RESPONSE, - - RULE_ENGINE_ERROR, - - SESSION_OPEN, SESSION_CLOSE, - - CLAIM_REQUEST(); - - private final boolean requiresRulesProcessing; - - SessionMsgType() { - this(false); - } - - SessionMsgType(boolean requiresRulesProcessing) { - this.requiresRulesProcessing = requiresRulesProcessing; - } - - public boolean requiresRulesProcessing() { - return requiresRulesProcessing; - } -} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapSessionMsgType.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapSessionMsgType.java new file mode 100644 index 0000000000..fcf33ea037 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapSessionMsgType.java @@ -0,0 +1,31 @@ +/** + * 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.transport.coap; + +public enum CoapSessionMsgType { + + GET_ATTRIBUTES_REQUEST, + POST_ATTRIBUTES_REQUEST, + SUBSCRIBE_ATTRIBUTES_REQUEST, + UNSUBSCRIBE_ATTRIBUTES_REQUEST, + POST_TELEMETRY_REQUEST, + SUBSCRIBE_RPC_COMMANDS_REQUEST, + UNSUBSCRIBE_RPC_COMMANDS_REQUEST, + TO_DEVICE_RPC_RESPONSE, + TO_SERVER_RPC_REQUEST, + CLAIM_REQUEST; + +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 7dde25bfd0..d6958137c7 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -33,7 +33,6 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; @@ -120,7 +119,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } else if (exchange.getRequestOptions().hasObserve()) { processExchangeGetRequest(exchange, featureType.get()); } else if (featureType.get() == FeatureType.ATTRIBUTES) { - processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); + processRequest(exchange, CoapSessionMsgType.GET_ATTRIBUTES_REQUEST); } else { log.trace("Invalid feature type parameter"); exchange.respond(CoAP.ResponseCode.BAD_REQUEST); @@ -129,13 +128,13 @@ public class CoapTransportResource extends AbstractCoapTransportResource { private void processExchangeGetRequest(CoapExchange exchange, FeatureType featureType) { boolean unsubscribe = exchange.getRequestOptions().getObserve() == 1; - SessionMsgType sessionMsgType; + CoapSessionMsgType coapSessionMsgType; if (featureType == FeatureType.RPC) { - sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; + coapSessionMsgType = unsubscribe ? CoapSessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : CoapSessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; } else { - sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; + coapSessionMsgType = unsubscribe ? CoapSessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : CoapSessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; } - processRequest(exchange, sessionMsgType); + processRequest(exchange, coapSessionMsgType); } @Override @@ -147,21 +146,21 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } else { switch (featureType.get()) { case ATTRIBUTES: - processRequest(exchange, SessionMsgType.POST_ATTRIBUTES_REQUEST); + processRequest(exchange, CoapSessionMsgType.POST_ATTRIBUTES_REQUEST); break; case TELEMETRY: - processRequest(exchange, SessionMsgType.POST_TELEMETRY_REQUEST); + processRequest(exchange, CoapSessionMsgType.POST_TELEMETRY_REQUEST); break; case RPC: Optional requestId = getRequestId(exchange.advanced().getRequest()); if (requestId.isPresent()) { - processRequest(exchange, SessionMsgType.TO_DEVICE_RPC_RESPONSE); + processRequest(exchange, CoapSessionMsgType.TO_DEVICE_RPC_RESPONSE); } else { - processRequest(exchange, SessionMsgType.TO_SERVER_RPC_REQUEST); + processRequest(exchange, CoapSessionMsgType.TO_SERVER_RPC_REQUEST); } break; case CLAIM: - processRequest(exchange, SessionMsgType.CLAIM_REQUEST); + processRequest(exchange, CoapSessionMsgType.CLAIM_REQUEST); break; case PROVISION: processProvision(exchange); @@ -195,7 +194,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } - private void processRequest(CoapExchange exchange, SessionMsgType type) { + private void processRequest(CoapExchange exchange, CoapSessionMsgType type) { log.trace("Processing {}", exchange.advanced().getRequest()); deferAccept(exchange); Exchange advanced = exchange.advanced(); @@ -218,7 +217,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } - private void processAccessTokenRequest(CoapExchange exchange, SessionMsgType type, Request request) { + private void processAccessTokenRequest(CoapExchange exchange, CoapSessionMsgType type, Request request) { Optional credentials = decodeCredentials(request); if (credentials.isEmpty()) { exchange.respond(CoAP.ResponseCode.UNAUTHORIZED); @@ -228,7 +227,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { new CoapDeviceAuthCallback(exchange, (deviceCredentials, deviceProfile) -> processRequest(exchange, type, request, deviceCredentials, deviceProfile))); } - private void processRequest(CoapExchange exchange, SessionMsgType type, Request request, ValidateDeviceCredentialsResponse deviceCredentials, DeviceProfile deviceProfile) { + private void processRequest(CoapExchange exchange, CoapSessionMsgType type, Request request, ValidateDeviceCredentialsResponse deviceCredentials, DeviceProfile deviceProfile) { TbCoapClientState clientState = null; try { clientState = clients.getOrCreateClient(type, deviceCredentials, deviceProfile); diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/CoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/CoapClientContext.java index 36ef8472de..a8531a7fba 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/CoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/CoapClientContext.java @@ -18,7 +18,7 @@ package org.thingsboard.server.transport.coap.client; import org.eclipse.californium.core.observe.ObserveRelation; import org.eclipse.californium.core.server.resources.CoapExchange; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.transport.coap.CoapSessionMsgType; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; @@ -33,7 +33,7 @@ public interface CoapClientContext { AtomicInteger getNotificationCounterByToken(String token); - TbCoapClientState getOrCreateClient(SessionMsgType type, ValidateDeviceCredentialsResponse deviceCredentials, DeviceProfile deviceProfile) throws AdaptorException; + TbCoapClientState getOrCreateClient(CoapSessionMsgType type, ValidateDeviceCredentialsResponse deviceCredentials, DeviceProfile deviceProfile) throws AdaptorException; TransportProtos.SessionInfoProto getNewSyncSession(TbCoapClientState clientState); 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 f70967b72d..26effe7058 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 @@ -45,7 +45,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.msg.session.FeatureType; -import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.transport.coap.CoapSessionMsgType; import org.thingsboard.server.common.transport.DeviceDeletedEvent; import org.thingsboard.server.common.transport.DeviceProfileUpdatedEvent; import org.thingsboard.server.common.transport.DeviceUpdatedEvent; @@ -388,7 +388,7 @@ public class DefaultCoapClientContext implements CoapClientContext { } @Override - public TbCoapClientState getOrCreateClient(SessionMsgType type, ValidateDeviceCredentialsResponse deviceCredentials, DeviceProfile deviceProfile) throws AdaptorException { + public TbCoapClientState getOrCreateClient(CoapSessionMsgType type, ValidateDeviceCredentialsResponse deviceCredentials, DeviceProfile deviceProfile) throws AdaptorException { DeviceId deviceId = deviceCredentials.getDeviceInfo().getDeviceId(); TbCoapClientState state = getClientState(deviceId); state.lock(); 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 3b827edbae..6500467af7 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 @@ -49,14 +49,14 @@ 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.rpc.RpcStatus; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; -import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.stats.MessagesStats; import org.thingsboard.server.common.stats.StatsFactory; @@ -583,7 +583,7 @@ public class DefaultTransportService implements TransportService { metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("ts", tsKv.getTs() + ""); JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); - sendToRuleEngine(tenantId, deviceId, customerId, sessionInfo, json, metaData, SessionMsgType.POST_TELEMETRY_REQUEST, packCallback); + sendToRuleEngine(tenantId, deviceId, customerId, sessionInfo, json, metaData, TbMsgType.POST_TELEMETRY_REQUEST, packCallback); } } } @@ -603,7 +603,7 @@ public class DefaultTransportService implements TransportService { } metaData.putValue(DataConstants.NOTIFY_DEVICE_METADATA_KEY, "false"); CustomerId customerId = getCustomerId(sessionInfo); - sendToRuleEngine(tenantId, deviceId, customerId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, + sendToRuleEngine(tenantId, deviceId, customerId, sessionInfo, json, metaData, TbMsgType.POST_ATTRIBUTES_REQUEST, new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, customerId, msg.getKvList().size(), callback))); } } @@ -723,7 +723,7 @@ public class DefaultTransportService implements TransportService { metaData.putValue("serviceId", serviceInfoProvider.getServiceId()); metaData.putValue("sessionId", sessionId.toString()); sendToRuleEngine(tenantId, deviceId, getCustomerId(sessionInfo), sessionInfo, json, metaData, - SessionMsgType.TO_SERVER_RPC_REQUEST, new TransportTbQueueCallback(callback)); + TbMsgType.TO_SERVER_RPC_REQUEST, new TransportTbQueueCallback(callback)); String requestId = sessionId + "-" + msg.getRequestId(); toServerRpcPendingMap.put(requestId, new RpcRequestMetadata(sessionId, msg.getRequestId())); scheduler.schedule(() -> processTimeout(requestId), clientSideRpcTimeout, TimeUnit.MILLISECONDS); @@ -1136,7 +1136,7 @@ public class DefaultTransportService implements TransportService { } private void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, CustomerId customerId, TransportProtos.SessionInfoProto sessionInfo, JsonObject json, - TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) { + TbMsgMetaData metaData, TbMsgType tbMsgType, TbQueueCallback callback) { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); RuleChainId ruleChainId; @@ -1151,7 +1151,7 @@ public class DefaultTransportService implements TransportService { queueName = deviceProfile.getDefaultQueueName(); } - TbMsg tbMsg = TbMsg.newMsg(queueName, sessionMsgType.name(), deviceId, customerId, metaData, gson.toJson(json), ruleChainId, null); + TbMsg tbMsg = TbMsg.newMsg(queueName, tbMsgType.name(), deviceId, customerId, metaData, gson.toJson(json), ruleChainId, null); sendToRuleEngine(tenantId, tbMsg, callback); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java index f35e7e608f..cf2ff1e7a0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.event.RuleChainDebugEventFilter; import org.thingsboard.server.common.data.event.RuleNodeDebugEventFilter; import org.thingsboard.server.common.data.event.StatisticsEventFilter; import org.thingsboard.server.common.data.id.EventId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.stats.StatsFactory; @@ -280,7 +281,7 @@ public class JpaBaseEventDao implements EventDao { private PageData findEventByFilter(UUID tenantId, UUID entityId, LifeCycleEventFilter eventFilter, TimePageLink pageLink) { boolean statusFilterEnabled = !StringUtils.isEmpty(eventFilter.getStatus()); - boolean statusFilter = statusFilterEnabled && eventFilter.getStatus().equalsIgnoreCase("Success"); + boolean statusFilter = statusFilterEnabled && eventFilter.getStatus().equalsIgnoreCase(TbNodeConnectionType.SUCCESS); return DaoUtil.toPageData( lcEventRepository.findEvents( tenantId, @@ -359,7 +360,7 @@ public class JpaBaseEventDao implements EventDao { private void removeEventsByFilter(UUID tenantId, UUID entityId, LifeCycleEventFilter eventFilter, Long startTime, Long endTime) { boolean statusFilterEnabled = !StringUtils.isEmpty(eventFilter.getStatus()); - boolean statusFilter = statusFilterEnabled && eventFilter.getStatus().equalsIgnoreCase("Success"); + boolean statusFilter = statusFilterEnabled && eventFilter.getStatus().equalsIgnoreCase(TbNodeConnectionType.SUCCESS); lcEventRepository.removeEvents( tenantId, entityId, diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java index 7ee61bac1d..7037f2f3fe 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java @@ -15,6 +15,7 @@ */ package org.thingsboard.rule.engine.api; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentClusteringMode; import org.thingsboard.server.common.data.plugin.ComponentScope; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -47,7 +48,7 @@ public @interface RuleNode { ComponentScope scope() default ComponentScope.TENANT; - String[] relationTypes() default {"Success", "Failure"}; + String[] relationTypes() default {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE}; String[] uiResources() default {}; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index 5f48f54436..bc27154ad8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -24,7 +24,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.script.ScriptLanguage; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java index c74bbe0008..ea53138d10 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java @@ -57,8 +57,8 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import static org.thingsboard.common.util.DonAsynchron.withCallback; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.FAILURE; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.FAILURE; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; @Slf4j public abstract class TbAbstractRelationActionNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 61004bfa11..4f69225274 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -49,7 +49,7 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETE import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; 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.rule.engine.api.TbNodeConnectionType.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index 58c42a1109..d9a99c3229 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -27,13 +27,13 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils; 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.session.SessionMsgType; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( @@ -77,7 +77,7 @@ public class TbMsgCountNode implements TbNode { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("delta", Long.toString(System.currentTimeMillis() - lastScheduledTs + delay)); - TbMsg tbMsg = TbMsg.newMsg(msg.getQueueName(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), ctx.getTenantId(), msg.getCustomerId(), metaData, gson.toJson(telemetryJson)); + TbMsg tbMsg = TbMsg.newMsg(msg.getQueueName(), POST_TELEMETRY_REQUEST.name(), ctx.getTenantId(), msg.getCustomerId(), metaData, gson.toJson(telemetryJson)); ctx.enqueueForTellNext(tbMsg, SUCCESS); scheduleTickMsg(ctx, tbMsg); } else { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 6bd8f52285..75b63c59e9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -41,7 +41,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static org.thingsboard.common.util.DonAsynchron.withCallback; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index af119d263f..5654b4095a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -24,7 +24,7 @@ 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.plugin.ComponentType; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index 17224c18b8..00ba3acc50 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -32,7 +32,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java index 869dc7ca07..d70dd75040 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java @@ -31,11 +31,11 @@ import org.thingsboard.server.common.data.plugin.ComponentType; type = ComponentType.FILTER, name = "asset profile switch", customRelations = true, - relationTypes = {}, + relationTypes = {"default"}, configClazz = EmptyNodeConfiguration.class, nodeDescription = "Route incoming messages based on the name of the asset profile", - nodeDetails = "Route incoming messages based on the name of the asset profile. The asset profile name is case-sensitive

" + - "Output connection types: Profile name of message originator or Failure", + nodeDetails = "Route incoming messages based on the name of the asset profile. The asset profile name is case-sensitive.

" + + "Output connections: Message originator profile name or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbAssetTypeSwitchNode extends TbAbstractTypeSwitchNode { @@ -43,7 +43,8 @@ public class TbAssetTypeSwitchNode extends TbAbstractTypeSwitchNode { @Override protected String getRelationType(TbContext ctx, EntityId originator) throws TbNodeException { if (!EntityType.ASSET.equals(originator.getEntityType())) { - throw new TbNodeException("Unsupported originator type: " + originator.getEntityType() + "! Only 'ASSET' type is allowed."); + throw new TbNodeException("Unsupported originator type: " + originator.getEntityType().getNormalName() + "!" + + " Only " + EntityType.ASSET.getNormalName() + " type is allowed."); } AssetProfile assetProfile = ctx.getAssetProfileCache().get(ctx.getTenantId(), (AssetId) originator); if (assetProfile == null) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java index f76366e622..ca58e47554 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java @@ -25,9 +25,9 @@ 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.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -41,7 +41,7 @@ import javax.annotation.Nullable; relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Checks alarm status.", nodeDetails = "Checks the alarm status to match one of the specified statuses.

" + - "Output connection types: True, False, Failure.", + "Output connections: True, False, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckAlarmStatusConfig") public class TbCheckAlarmStatusNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java index c461b258a8..a5ee3b969b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java @@ -22,7 +22,7 @@ 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -39,7 +39,7 @@ import java.util.Map; nodeDescription = "Checks the presence of the specified fields in the message and/or metadata.", nodeDetails = "By default, the rule node checks that all specified fields are present. " + "Uncheck the 'Check that all selected fields are present' if the presence of at least one field is sufficient.

" + - "Output connection types: True, False, Failure", + "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckMessageConfig") public class TbCheckMessageNode implements TbNode { @@ -60,11 +60,10 @@ public class TbCheckMessageNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { try { - if (config.isCheckAllKeys()) { - ctx.tellNext(msg, allKeysData(msg) && allKeysMetadata(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); - } else { - ctx.tellNext(msg, atLeastOneData(msg) || atLeastOneMetadata(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); - } + String relationType = config.isCheckAllKeys() ? + allKeysData(msg) && allKeysMetadata(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE : + atLeastOneData(msg) || atLeastOneMetadata(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE; + ctx.tellNext(msg, relationType); } catch (Exception e) { ctx.tellFailure(msg, e); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeConfiguration.java index f145be3fd2..25a6efa417 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeConfiguration.java @@ -22,7 +22,7 @@ import java.util.Collections; import java.util.List; @Data -public class TbCheckMessageNodeConfiguration implements NodeConfiguration { +public class TbCheckMessageNodeConfiguration implements NodeConfiguration { private List messageNames; private List metadataNames; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index 43bec5a3c1..3dfa6cb233 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -17,14 +17,13 @@ package org.thingsboard.rule.engine.filter; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; 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.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -51,7 +50,7 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; nodeDetails = "If 'check relation to specific entity' is selected, you should specify a related entity. " + "Otherwise, the rule node checks the presence of a relation to any entity. " + "In both cases, relation lookup is based on configured direction and type.

" + - "Output connection types: True, False, Failure", + "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckRelationConfig") public class TbCheckRelationNode implements TbNode { @@ -91,13 +90,10 @@ public class TbCheckRelationNode implements TbNode { } private ListenableFuture processList(TbContext ctx, TbMsg msg) { - if (EntitySearchDirection.FROM.name().equals(config.getDirection())) { - return Futures.transformAsync(ctx.getRelationService() - .findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON), this::isEmptyList, MoreExecutors.directExecutor()); - } else { - return Futures.transformAsync(ctx.getRelationService() - .findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON), this::isEmptyList, MoreExecutors.directExecutor()); - } + ListenableFuture> relationListFuture = EntitySearchDirection.FROM.name().equals(config.getDirection()) ? ctx.getRelationService() + .findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON) : ctx.getRelationService() + .findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON); + return Futures.transformAsync(relationListFuture, this::isEmptyList, ctx.getDbCallbackExecutor()); } private ListenableFuture isEmptyList(List entityRelations) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java index aeda247583..ad5574ec8c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.filter; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; /** @@ -35,7 +36,7 @@ public class TbCheckRelationNodeConfiguration implements NodeConfigurationFailure", + "Output connections: Message originator profile name or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbDeviceTypeSwitchNode extends TbAbstractTypeSwitchNode { @@ -43,7 +43,8 @@ public class TbDeviceTypeSwitchNode extends TbAbstractTypeSwitchNode { @Override protected String getRelationType(TbContext ctx, EntityId originator) throws TbNodeException { if (!EntityType.DEVICE.equals(originator.getEntityType())) { - throw new TbNodeException("Unsupported originator type: " + originator.getEntityType() + "! Only 'DEVICE' type is allowed."); + throw new TbNodeException("Unsupported originator type: " + originator.getEntityType().getNormalName() + + "! Only " + EntityType.DEVICE.getNormalName() + " type is allowed."); } DeviceProfile deviceProfile = ctx.getDeviceProfileCache().get(ctx.getTenantId(), (DeviceId) originator); if (deviceProfile == null) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index 1b8461b44b..bcfaf78a65 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -22,7 +22,7 @@ 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.script.ScriptLanguage; @@ -43,7 +43,7 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; "Message payload can be accessed via msg property. For example msg.temperature < 10;
" + "Message metadata can be accessed via metadata property. For example metadata.customerName === 'John';
" + "Message type can be accessed via msgType property.

" + - "Output connection types: True, False, Failure", + "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeScriptConfig" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index 8c0f058ed3..f55a172c02 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -45,7 +45,7 @@ import java.util.Set; "Message payload can be accessed via msg property. For example msg.temperature < 10;
" + "Message metadata can be accessed via metadata property. For example metadata.customerName === 'John';
" + "Message type can be accessed via msgType property.

" + - "Output connection types: Custom connection(s) defined by switch node or Failure", + "Output connections: Custom connection(s) defined by switch node or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeSwitchConfig") public class TbJsSwitchNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java index 5074991cb1..ca8ba9d561 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java @@ -21,7 +21,7 @@ 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -32,12 +32,12 @@ import org.thingsboard.server.common.msg.TbMsg; @Slf4j @RuleNode( type = ComponentType.FILTER, - name = "message type", + name = "message type filter", configClazz = TbMsgTypeFilterNodeConfiguration.class, relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Filter incoming messages by Message Type", nodeDetails = "If incoming message type is expected - send Message via True chain, otherwise False chain is used.

" + - "Output connection types: True, False, Failure", + "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeMessageTypeConfig") public class TbMsgTypeFilterNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java index fb326a6d50..1c0b080653 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java @@ -17,11 +17,14 @@ package org.thingsboard.rule.engine.filter; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; -import org.thingsboard.server.common.msg.session.SessionMsgType; import java.util.Arrays; import java.util.List; +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.TO_SERVER_RPC_REQUEST; + /** * Created by ashvayka on 19.01.18. */ @@ -32,11 +35,11 @@ public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration etc. via corresponding chain, otherwise Other chain is used.", + nodeDetails = "Sends messages with message types \"Post attributes\", \"Post telemetry\", \"RPC Request\"" + + " etc. via corresponding chain, otherwise Other chain is used.

" + + "Output connections: Message type connection, Other - if message type is custom or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbMsgTypeSwitchNode implements TbNode { @@ -48,7 +50,7 @@ public class TbMsgTypeSwitchNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ctx.tellNext(msg, TbMsgType.getNodeConnection(msg.getType())); + ctx.tellNext(msg, TbMsgType.getRuleNodeConnection(msg.getType())); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java index 8c32d8a1c9..183721e72a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java @@ -21,7 +21,7 @@ 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -30,12 +30,12 @@ import org.thingsboard.server.common.msg.TbMsg; @Slf4j @RuleNode( type = ComponentType.FILTER, - name = "entity type", + name = "entity type filter", configClazz = TbOriginatorTypeFilterNodeConfiguration.class, relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Filter incoming messages by the type of message originator entity", nodeDetails = "Checks that the entity type of the incoming message originator matches one of the values specified in the filter.

" + - "Output connection types: True, False, Failure", + "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeOriginatorTypeConfig") public class TbOriginatorTypeFilterNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java index 11365a2b1d..9e14c6a7ab 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; relationTypes = {}, // should always be empty. We add the relation types for this node in AnnotationComponentDiscoveryService. nodeDescription = "Route incoming messages by Message Originator Type", nodeDetails = "Routes messages to chain according to the entity type ('Device', 'Asset', etc.).

" + - "Output connection types: entityType of the message originator or Failure", + "Output connections: Message originator type or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbOriginatorTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java index e0ffc564db..a92cafefd3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java @@ -21,7 +21,7 @@ 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java index b4d217d799..20e7039be4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java @@ -19,7 +19,7 @@ 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.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -59,7 +59,7 @@ import org.thingsboard.server.common.msg.TbMsg; "{\"latitude\": 48.198618758582384, \"longitude\": 24.65322245153503, \"radius\": 100.0, \"radiusUnit\": \"METER\" }" + "

" + "Available radius units: METER, KILOMETER, FOOT, MILE, NAUTICAL_MILE;

" + - "Output connection types: True, False, Failure", + "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeGpsGeofencingConfig") public class TbGpsGeofencingFilterNode extends AbstractGeofencingNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 7afd61da2d..051e646b78 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -31,7 +31,7 @@ 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.exception.ThingsboardKafkaClientError; import org.thingsboard.server.common.data.plugin.ComponentType; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index d56b0d6890..ae661e48e4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -34,7 +34,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; @Slf4j diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index 1a2ba6d646..d4bdd320d1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -25,13 +25,14 @@ 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.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.TsKvEntry; +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.session.SessionMsgType; import org.thingsboard.server.dao.timeseries.TimeseriesService; import java.math.BigDecimal; @@ -45,7 +46,7 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; @Slf4j @RuleNode(type = ComponentType.ENRICHMENT, - name = "calculate delta", relationTypes = {"Success", "Failure", "Other"}, + name = "calculate delta", relationTypes = {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE, TbNodeConnectionType.OTHER}, configClazz = CalculateDeltaNodeConfiguration.class, nodeDescription = "Calculates delta and amount of time passed between previous timeseries key reading " + "and current value for this key from the incoming message", @@ -73,14 +74,14 @@ public class CalculateDeltaNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { - ctx.tellNext(msg, "Other"); + if (!msg.getType().equals(TbMsgType.POST_TELEMETRY_REQUEST.name())) { + ctx.tellNext(msg, TbNodeConnectionType.OTHER); return; } JsonNode json = JacksonUtil.toJsonNode(msg.getData()); String inputKey = config.getInputValueKey(); if (!json.has(inputKey)) { - ctx.tellNext(msg, "Other"); + ctx.tellNext(msg, TbNodeConnectionType.OTHER); return; } withCallback(getLastValue(msg.getOriginator()), diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index c7580fb7d3..641daa82ca 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -43,7 +43,7 @@ import org.springframework.web.util.UriComponentsBuilder; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.credentials.BasicCredentials; import org.thingsboard.rule.engine.credentials.ClientCredentials; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java index 936b192c85..5f9a2f63ed 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -27,7 +27,7 @@ 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 9cf9bfa1bd..5ddb1c701f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import java.util.ArrayList; @@ -36,6 +35,7 @@ import java.util.List; import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE; import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY; import static org.thingsboard.server.common.data.DataConstants.SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; @Slf4j @RuleNode( @@ -65,7 +65,7 @@ public class TbMsgAttributesNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) { + if (!msg.getType().equals(POST_ATTRIBUTES_REQUEST.name())) { ctx.tellFailure(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType())); return; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java index dcc84813cd..706135eaa2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java @@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import java.util.ArrayList; @@ -39,6 +38,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; + @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -81,7 +82,7 @@ public class TbMsgTimeseriesNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { + if (!msg.getType().equals(POST_TELEMETRY_REQUEST.name())) { ctx.tellFailure(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType())); return; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java index d96060ffc2..72570d4e2a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java @@ -22,7 +22,7 @@ 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.TbMsgCallback; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java index 9c86891bc0..1ee7ab7b03 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java @@ -25,7 +25,7 @@ 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/TestDbCallbackExecutor.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/TestDbCallbackExecutor.java new file mode 100644 index 0000000000..ed9053e2e2 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/TestDbCallbackExecutor.java @@ -0,0 +1,40 @@ +/** + * 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; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.common.util.ListeningExecutor; + +import java.util.concurrent.Callable; + +public class TestDbCallbackExecutor implements ListeningExecutor { + + @Override + public ListenableFuture executeAsync(Callable task) { + try { + return Futures.immediateFuture(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void execute(Runnable command) { + command.run(); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 132383c064..9173b7143c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -18,7 +18,6 @@ package org.thingsboard.rule.engine.action; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import org.apache.commons.lang3.NotImplementedException; import org.junit.Before; import org.junit.Test; @@ -29,6 +28,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; @@ -52,7 +52,6 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import javax.script.ScriptException; import java.io.IOException; -import java.util.concurrent.Callable; import java.util.function.Consumer; import static org.junit.Assert.assertEquals; @@ -105,21 +104,7 @@ public class TbAlarmNodeTest { @Before public void before() { - dbExecutor = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(Runnable command) { - command.run(); - } - }; + dbExecutor = new TestDbCallbackExecutor(); } @Test diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java index 3b8a8c9193..542ca75a51 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java @@ -26,17 +26,17 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.DeviceId; 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.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -79,21 +79,7 @@ public class TbCreateRelationNodeTest { @Before public void before() { - dbExecutor = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(Runnable command) { - command.run(); - } - }; + dbExecutor = new TestDbCallbackExecutor(); } @Test diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java index 98c0c62231..f44c58e663 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java @@ -40,7 +40,6 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeService; @@ -54,6 +53,7 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATE import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @RunWith(MockitoJUnitRunner.class) public class TbMsgPushToEdgeNodeTest { @@ -89,7 +89,7 @@ public class TbMsgPushToEdgeNodeTest { Mockito.when(ctx.getEdgeService()).thenReturn(edgeService); Mockito.when(edgeService.findRelatedEdgeIdsByEntityId(tenantId, deviceId, new PageLink(TbMsgPushToEdgeNode.DEFAULT_PAGE_SIZE))).thenReturn(new PageData<>()); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", null, null); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java index 4ddf75f29c..3e4dd21fd2 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.never; 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.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbAssetTypeSwitchNodeTest { @@ -121,6 +122,6 @@ class TbAssetTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(), "{}", callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}", callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java new file mode 100644 index 0000000000..0677f0b0c9 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java @@ -0,0 +1,167 @@ +/** + * 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.filter; + +import com.google.common.util.concurrent.Futures; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; +import org.thingsboard.rule.engine.api.RuleEngineAlarmService; +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.alarm.Alarm; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.UUID; + +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; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbCheckAlarmStatusNodeTest { + + private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); + private static final AlarmId ALARM_ID = new AlarmId(UUID.randomUUID()); + private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); + + private static TbCheckAlarmStatusNode node; + + private static TbContext ctx; + private static RuleEngineAlarmService alarmService; + + @BeforeEach + public void setUp() throws TbNodeException { + var config = new TbCheckAlarmStatusNodeConfig().defaultConfiguration(); + + ctx = mock(TbContext.class); + alarmService = mock(RuleEngineAlarmService.class); + + when(ctx.getTenantId()).thenReturn(TENANT_ID); + when(ctx.getAlarmService()).thenReturn(alarmService); + when(ctx.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + node = new TbCheckAlarmStatusNode(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenActiveAlarm_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var alarm = new Alarm(); + alarm.setId(ALARM_ID); + alarm.setOriginator(DEVICE_ID); + alarm.setType("General Alarm"); + + String msgData = JacksonUtil.toString(alarm); + TbMsg msg = getTbMsg(DEVICE_ID, msgData); + + when(alarmService.findAlarmByIdAsync(TENANT_ID, ALARM_ID)).thenReturn(Futures.immediateFuture(alarm)); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenClearedAlarm_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var alarm = new Alarm(); + alarm.setId(ALARM_ID); + alarm.setOriginator(DEVICE_ID); + alarm.setType("General Alarm"); + alarm.setCleared(true); + + String msgData = JacksonUtil.toString(alarm); + TbMsg msg = getTbMsg(DEVICE_ID, msgData); + + when(alarmService.findAlarmByIdAsync(TENANT_ID, ALARM_ID)).thenReturn(Futures.immediateFuture(alarm)); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenDeletedAlarm_whenOnMsg_then_Failure() throws TbNodeException { + // GIVEN + var alarm = new Alarm(); + alarm.setId(ALARM_ID); + alarm.setOriginator(DEVICE_ID); + alarm.setType("General Alarm"); + alarm.setCleared(true); + + String msgData = JacksonUtil.toString(alarm); + TbMsg msg = getTbMsg(DEVICE_ID, msgData); + + when(alarmService.findAlarmByIdAsync(TENANT_ID, ALARM_ID)).thenReturn(Futures.immediateFuture(null)); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class); + verify(ctx, times(1)).tellFailure(newMsgCaptor.capture(), throwableCaptor.capture()); + verify(ctx, never()).tellSuccess(any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + Throwable value = throwableCaptor.getValue(); + assertThat(value).isInstanceOf(TbNodeException.class).hasMessage("No such alarm found."); + } + + private TbMsg getTbMsg(EntityId entityId, String msgData) { + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), msgData); + } + + +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java index e7b3ccd7f1..fb77b5dc5d 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.never; 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.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbDeviceTypeSwitchNodeTest { @@ -121,6 +122,6 @@ class TbDeviceTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(), "{}", callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}", callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 81e4c70ce0..0b9b2ec143 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -27,7 +27,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java new file mode 100644 index 0000000000..7814c82662 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java @@ -0,0 +1,103 @@ +/** + * 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.filter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +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.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.UUID; + +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.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbMsgTypeFilterNodeTest { + + private DeviceId deviceId; + private TbContext ctx; + private TbMsgTypeFilterNode node; + + @BeforeEach + void setUp() throws TbNodeException { + ctx = mock(TbContext.class); + var config = new TbMsgTypeFilterNodeConfiguration().defaultConfiguration(); + deviceId = new DeviceId(UUID.randomUUID()); + node = new TbMsgTypeFilterNode(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenPostAttributes_whenOnMsg_then_True() { + // GIVEN + TbMsg msg = getTbMsg(deviceId, POST_ATTRIBUTES_REQUEST); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenAttributesUpdated_whenOnMsg_then_False() { + // GIVEN + TbMsg msg = getTbMsg(deviceId, ATTRIBUTES_UPDATED); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + private TbMsg getTbMsg(EntityId entityId, TbMsgType msgType) { + return TbMsg.newMsg(msgType.name(), entityId, new TbMsgMetaData(), "{}"); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java new file mode 100644 index 0000000000..86ba9d1fd0 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java @@ -0,0 +1,102 @@ +/** + * 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.filter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +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.id.AssetId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.UUID; + +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.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbOriginatorTypeFilterNodeTest { + + private TbContext ctx; + private TbOriginatorTypeFilterNode node; + + @BeforeEach + void setUp() throws TbNodeException { + ctx = mock(TbContext.class); + var config = new TbOriginatorTypeFilterNodeConfiguration().defaultConfiguration(); + node = new TbOriginatorTypeFilterNode(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenDevice_whenOnMsg_then_True() { + // GIVEN + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenAsset_whenOnMsg_then_False() { + // GIVEN + AssetId assetId = new AssetId(UUID.randomUUID()); + TbMsg msg = getTbMsg(assetId); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + private TbMsg getTbMsg(EntityId entityId) { + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}"); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java index 86765446e0..6182d8cca5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java @@ -16,10 +16,8 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; import org.assertj.core.api.Assertions; -import org.jetbrains.annotations.NotNull; 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.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -41,13 +40,13 @@ 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.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.timeseries.TimeseriesService; import java.util.List; import java.util.UUID; -import java.util.concurrent.Callable; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -64,27 +63,15 @@ import static org.mockito.Mockito.reset; 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.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class CalculateDeltaNodeTest { private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock @@ -117,13 +104,13 @@ public class CalculateDeltaNodeTest { public void givenInvalidMsgType_whenOnMsg_thenShouldTellNextOther() { // GIVEN var msgData = "{\"pulseCounter\": 42}"; - var msg = TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); // THEN - verify(ctxMock, times(1)).tellNext(eq(msg), eq("Other")); + verify(ctxMock, times(1)).tellNext(eq(msg), eq(TbNodeConnectionType.OTHER)); verify(ctxMock, never()).tellSuccess(any()); verify(ctxMock, never()).tellFailure(any(), any()); } @@ -132,13 +119,13 @@ public class CalculateDeltaNodeTest { public void givenInvalidMsgDataType_whenOnMsg_thenShouldTellNextOther() { // GIVEN var msgData = "[]"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); // THEN - verify(ctxMock, times(1)).tellNext(eq(msg), eq("Other")); + verify(ctxMock, times(1)).tellNext(eq(msg), eq(TbNodeConnectionType.OTHER)); verify(ctxMock, never()).tellSuccess(any()); verify(ctxMock, never()).tellFailure(any(), any()); } @@ -147,13 +134,13 @@ public class CalculateDeltaNodeTest { @Test public void givenInputKeyIsNotPresent_whenOnMsg_thenShouldTellNextOther() { // GIVEN - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); // WHEN node.onMsg(ctxMock, msg); // THEN - verify(ctxMock, times(1)).tellNext(eq(msg), eq("Other")); + verify(ctxMock, times(1)).tellNext(eq(msg), eq(TbNodeConnectionType.OTHER)); verify(ctxMock, never()).tellSuccess(any()); verify(ctxMock, never()).tellFailure(any(), any()); } @@ -171,7 +158,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", 40.5))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); @@ -201,7 +188,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 40L))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); @@ -231,7 +218,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("temperature", "40.0"))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); @@ -265,7 +252,7 @@ public class CalculateDeltaNodeTest { var msgData = "{\"temperature\": 42,\"airPressure\":123}"; var firstMsgMetaData = new TbMsgMetaData(); firstMsgMetaData.putValue("ts", String.valueOf(3L)); - var firstMsg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, firstMsgMetaData, msgData); + var firstMsg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, firstMsgMetaData, msgData); // WHEN node.onMsg(ctxMock, firstMsg); @@ -289,7 +276,7 @@ public class CalculateDeltaNodeTest { var secondMsgMetaData = new TbMsgMetaData(); secondMsgMetaData.putValue("ts", String.valueOf(6L)); - var secondMsg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, secondMsgMetaData, msgData); + var secondMsg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, secondMsgMetaData, msgData); // WHEN node.onMsg(ctxMock, secondMsg); @@ -320,7 +307,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", null))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); @@ -348,7 +335,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("pulseCounter", 200L))); var msgData = "{\"pulseCounter\":\"123\"}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); @@ -377,7 +364,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("pulseCounter", "high"))); var msgData = "{\"pulseCounter\":\"123\"}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN-THEN Assertions.assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) @@ -391,7 +378,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry("pulseCounter", false))); var msgData = "{\"pulseCounter\":true}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN-THEN Assertions.assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) @@ -405,7 +392,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new JsonDataEntry("pulseCounter", "{\"isActive\":false}"))); var msgData = "{\"pulseCounter\":{\"isActive\":true}}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN-THEN Assertions.assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java index 4c9d138236..dfd054edc7 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java @@ -51,6 +51,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; import static org.thingsboard.server.common.data.security.DeviceCredentialsType.ACCESS_TOKEN; @ExtendWith(MockitoExtension.class) @@ -171,7 +172,7 @@ public class TbFetchDeviceCredentialsNodeTest { final var metaData = new TbMsgMetaData(mdMap); final String data = "{\"TestAttribute_1\": \"humidity\", \"TestAttribute_2\": \"voltage\"}"; - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, metaData, data, callbackMock); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, metaData, data, callbackMock); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 156c3f03d7..27a13893c8 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -17,9 +17,7 @@ package org.thingsboard.rule.engine.metadata; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,6 +28,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -62,7 +61,6 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.UUID; -import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -77,6 +75,7 @@ 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; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetCustomerAttributeNodeTest { @@ -84,21 +83,7 @@ public class TbGetCustomerAttributeNodeTest { private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); private static final CustomerId CUSTOMER_ID = new CustomerId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock @@ -223,7 +208,7 @@ public class TbGetCustomerAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -238,7 +223,7 @@ public class TbGetCustomerAttributeNodeTest { // GIVEN var userId = new UserId(UUID.randomUUID()); - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", userId, new TbMsgMetaData(), "{}"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), userId, new TbMsgMetaData(), "{}"); when(ctxMock.getTenantId()).thenReturn(TENANT_ID); @@ -482,7 +467,7 @@ public class TbGetCustomerAttributeNodeTest { var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index 2830dddaeb..d90d9eb767 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -17,8 +17,6 @@ package org.thingsboard.rule.engine.metadata; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -28,6 +26,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -60,7 +59,6 @@ import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; -import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -70,27 +68,14 @@ import static org.mockito.Mockito.never; 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.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetCustomerDetailsNodeTest { private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock @@ -471,7 +456,7 @@ public class TbGetCustomerDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), originator, msgMetaData, msgData); } private void mockFindCustomer() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index 4f03fe54c0..fc61bc4849 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -16,9 +16,6 @@ package org.thingsboard.rule.engine.metadata; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -28,6 +25,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -43,7 +41,6 @@ import org.thingsboard.server.dao.device.DeviceService; import java.util.Collections; import java.util.Map; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import static org.assertj.core.api.Assertions.assertThat; @@ -54,27 +51,14 @@ import static org.mockito.Mockito.never; 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.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetOriginatorFieldsNodeTest { private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId DUMMY_TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock @@ -178,7 +162,7 @@ public class TbGetOriginatorFieldsNodeTest { node.fetchTo = FetchTo.DATA; var msgMetaData = new TbMsgMetaData(); var msgData = "{\"temp\":42,\"humidity\":77}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -221,7 +205,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -269,7 +253,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -327,7 +311,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", new DashboardId(UUID.randomUUID()), msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), new DashboardId(UUID.randomUUID()), msgMetaData, msgData); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index 23ceb082bc..d2c452afd4 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -17,9 +17,7 @@ package org.thingsboard.rule.engine.metadata; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,6 +28,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -60,7 +59,6 @@ import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; 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.common.msg.session.SessionMsgType; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.relation.RelationService; @@ -72,7 +70,6 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.UUID; -import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -87,27 +84,14 @@ 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; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetRelatedAttributeNodeTest { private static final EntityId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock @@ -238,7 +222,7 @@ public class TbGetRelatedAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -607,7 +591,7 @@ public class TbGetRelatedAttributeNodeTest { msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; } - msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index d8532247ae..0ee3512288 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -17,9 +17,7 @@ package org.thingsboard.rule.engine.metadata; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,7 +27,7 @@ import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -53,7 +51,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -65,27 +62,14 @@ 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; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetTenantAttributeNodeTest { private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock @@ -204,7 +188,7 @@ public class TbGetTenantAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -412,7 +396,7 @@ public class TbGetTenantAttributeNodeTest { var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java index 6e1e619eab..0a756575e2 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java @@ -49,6 +49,7 @@ import static org.mockito.Mockito.never; 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.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetTenantDetailsNodeTest { @@ -286,7 +287,7 @@ public class TbGetTenantDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); } private void mockFindTenant() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java index b480719f64..0d94d9de16 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java @@ -43,7 +43,6 @@ import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceService; @@ -109,7 +108,7 @@ public class DeviceStateTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); DeviceState deviceState = createDeviceState(deviceId, alarmConfig); - TbMsg attributeUpdateMsg = TbMsg.newMsg(SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), + TbMsg attributeUpdateMsg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), deviceId, new TbMsgMetaData(), "{ \"enabled\": false }"); deviceState.process(ctx, attributeUpdateMsg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index 80ed769a71..20c529646c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -64,7 +64,6 @@ import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; @@ -86,6 +85,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @RunWith(MockitoJUnitRunner.class) public class TbDeviceProfileNodeTest { @@ -141,7 +141,7 @@ public class TbDeviceProfileNodeTest { Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 42); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); @@ -198,7 +198,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 42); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); @@ -211,7 +211,7 @@ public class TbDeviceProfileNodeTest { registerCreateAlarmMock(alarmService.updateAlarm(any()), false); - TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); verify(ctx).tellSuccess(msg2); @@ -292,7 +292,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 21); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -379,7 +379,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 21); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -448,7 +448,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -542,7 +542,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -557,7 +557,7 @@ public class TbDeviceProfileNodeTest { Thread.sleep(halfOfAlarmDelay); - TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -666,7 +666,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -681,7 +681,7 @@ public class TbDeviceProfileNodeTest { Thread.sleep(halfOfAlarmDelay); - TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -775,7 +775,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -784,7 +784,7 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); data.put("temperature", 151); - TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -891,7 +891,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -900,7 +900,7 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); data.put("temperature", 151); - TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -987,7 +987,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1002,7 +1002,7 @@ public class TbDeviceProfileNodeTest { Thread.sleep(halfOfAlarmDelay); - TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -1085,7 +1085,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1167,7 +1167,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); // Mockito.reset(ctx); @@ -1261,7 +1261,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1341,7 +1341,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 25); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1414,7 +1414,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 40); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1497,7 +1497,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150L); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1582,7 +1582,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150L); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java index 27faa8313a..a72d343d10 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java @@ -31,10 +31,10 @@ 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.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.dao.edge.EdgeEventService; import java.util.UUID; @@ -80,7 +80,7 @@ public class TbSendRPCReplyNodeTest { Mockito.when(ctx.getRpcService()).thenReturn(rpcService); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, getDefaultMetadata(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, getDefaultMetadata(), TbMsgDataType.JSON, DUMMY_DATA, null, null); node.onMsg(ctx, msg); @@ -99,7 +99,7 @@ public class TbSendRPCReplyNodeTest { TbMsgMetaData defaultMetadata = getDefaultMetadata(); defaultMetadata.putValue(DataConstants.EDGE_ID, UUID.randomUUID().toString()); defaultMetadata.putValue(DataConstants.DEVICE_ID, UUID.randomUUID().toString()); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetadata, + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetadata, TbMsgDataType.JSON, DUMMY_DATA, null, null); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java index c0839f5b61..9b23a3aa31 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java @@ -53,6 +53,7 @@ import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_MET import static org.thingsboard.server.common.data.DataConstants.SCOPE; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; @Slf4j public class TbMsgDeleteAttributesNodeTest { @@ -140,7 +141,7 @@ public class TbMsgDeleteAttributesNodeTest { } final String data = "{\"TestAttribute_2\": \"humidity\", \"TestAttribute_3\": \"voltage\"}"; - TbMsg msg = TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", deviceId, metaData, data, callback); + TbMsg msg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), deviceId, metaData, data, callback); node.onMsg(ctx, msg); ArgumentCaptor successCaptor = ArgumentCaptor.forClass(Runnable.class); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java index f4cd097878..071ae9ce96 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java @@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.transform; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,6 +25,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -41,7 +41,6 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.asset.AssetService; import java.util.NoSuchElementException; -import java.util.concurrent.Callable; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -66,21 +65,7 @@ public class TbChangeOriginatorNodeTest { @Before public void before() { - dbExecutor = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(Runnable command) { - command.run(); - } - }; + dbExecutor = new TestDbCallbackExecutor(); } @Test diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java index 2666cae477..a2bfb26b4e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbCopyKeysNodeTest { DeviceId deviceId; @@ -157,7 +158,7 @@ public class TbCopyKeysNodeTest { "voltageDataValue", "220", "city", "NY" ); - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java index 7152c25acd..c73e1eec15 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbDeleteKeysNodeTest { DeviceId deviceId; @@ -140,7 +141,7 @@ public class TbDeleteKeysNodeTest { "voltageDataValue", "220", "city", "NY" ); - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java index 0ca486a3e0..91db8dfd26 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbJsonPathNodeTest { DeviceId deviceId; @@ -170,6 +171,6 @@ public class TbJsonPathNodeTest { Map mdMap = Map.of("country", "US", "city", "NY" ); - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java index f48a9f670c..ba17187297 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java @@ -30,7 +30,6 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.deduplication.DeduplicationStrategy; import org.thingsboard.rule.engine.deduplication.TbMsgDeduplicationNode; import org.thingsboard.rule.engine.deduplication.TbMsgDeduplicationNodeConfiguration; @@ -39,9 +38,9 @@ import org.thingsboard.server.common.data.id.DeviceId; 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.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.msg.session.SessionMsgType; import java.util.ArrayList; import java.util.List; @@ -66,6 +65,8 @@ 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.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @Slf4j public class TbMsgDeduplicationNodeTest { @@ -242,7 +243,7 @@ public class TbMsgDeduplicationNodeTest { config.setInterval(deduplicationInterval); config.setStrategy(DeduplicationStrategy.ALL); - config.setOutMsgType(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()); + config.setOutMsgType(POST_ATTRIBUTES_REQUEST.name()); config.setQueueName(DataConstants.HP_QUEUE_NAME); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -282,7 +283,7 @@ public class TbMsgDeduplicationNodeTest { config.setInterval(deduplicationInterval); config.setStrategy(DeduplicationStrategy.ALL); - config.setOutMsgType(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()); + config.setOutMsgType(POST_ATTRIBUTES_REQUEST.name()); config.setQueueName(DataConstants.HP_QUEUE_NAME); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -414,7 +415,7 @@ public class TbMsgDeduplicationNodeTest { metaData.putValue("ts", String.valueOf(ts)); return TbMsg.newMsg( DataConstants.MAIN_QUEUE_NAME, - SessionMsgType.POST_TELEMETRY_REQUEST.name(), + POST_TELEMETRY_REQUEST.name(), deviceId, metaData, JacksonUtil.toString(dataNode)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java index 9eade69e4b..0caa7f74f3 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java @@ -40,6 +40,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbRenameKeysNodeTest { DeviceId deviceId; @@ -154,6 +155,6 @@ public class TbRenameKeysNodeTest { "country", "US", "city", "NY" ); - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java index 67ebf6f6fc..cf1eee085c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java @@ -43,6 +43,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbSplitArrayMsgNodeTest { DeviceId deviceId; @@ -132,6 +133,6 @@ public class TbSplitArrayMsgNodeTest { "country", "US", "city", "NY" ); - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java index 957f98d721..c675b97d08 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java @@ -16,13 +16,12 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import org.jetbrains.annotations.NotNull; 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.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.Customer; @@ -41,7 +40,6 @@ import org.thingsboard.server.dao.user.UserService; import java.util.EnumSet; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -60,21 +58,7 @@ public class EntitiesCustomerIdAsyncLoaderTest { EntityType.ASSET, EntityType.DEVICE ); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock 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 e835fa6e78..d60f2256e1 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 @@ -16,8 +16,6 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -25,6 +23,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; @@ -61,7 +60,6 @@ import org.thingsboard.server.dao.user.UserService; import java.util.EnumSet; import java.util.NoSuchElementException; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import static org.assertj.core.api.Assertions.assertThat; @@ -75,21 +73,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class EntitiesFieldsAsyncLoaderTest { - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); private static EnumSet SUPPORTED_ENTITY_TYPES; private static UUID RANDOM_UUID; private static TenantId TENANT_ID; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java index 546a726f07..c54072fb53 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java @@ -16,13 +16,12 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import org.jetbrains.annotations.NotNull; 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.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.data.DeviceRelationsQuery; import org.thingsboard.server.common.data.Device; @@ -37,7 +36,6 @@ import org.thingsboard.server.dao.device.DeviceService; import java.util.List; import java.util.UUID; -import java.util.concurrent.Callable; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -51,21 +49,7 @@ public class EntitiesRelatedDeviceIdAsyncLoaderTest { private static final EntityId DUMMY_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoaderTest.java index b29e451eaf..e51a3704d1 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoaderTest.java @@ -17,11 +17,11 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.data.RelationsQuery; import org.thingsboard.server.common.data.Device; @@ -41,7 +41,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; -import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -57,21 +56,7 @@ public class EntitiesRelatedEntityIdAsyncLoaderTest { private static final EntityId ASSET_ORIGINATOR_ID = new AssetId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); private TbContext ctxMock; private RelationService relationServiceMock; From 5acd5b36585f7a08f50ace494beece9979eae047 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 26 Jun 2023 11:45:20 +0300 Subject: [PATCH 019/168] refactoring --- .../server/controller/DeviceControllerTest.java | 6 +++--- .../server/dao/device/DeviceServiceImpl.java | 14 ++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 00ccdb1119..96aa5638db 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -662,11 +662,11 @@ public class DeviceControllerTest extends AbstractControllerTest { doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); assertThat(commands).hasSize(3); - assertThat(commands).containsExactly(String.format("mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -u %s -m \"{temperature:15}\"", + assertThat(commands).containsExactly(String.format("mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId()), - String.format("curl -v -X POST http://localhost:80/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:16}\"", + String.format("curl -v -X POST http://localhost:80/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", credentials.getCredentialsId()), - String.format("echo -n \"{temperature:17}\" | coap-client -m post coap://localhost:5683/api/v1/%s/telemetry -f-", + String.format("echo -n \"{temperature:25}\" | coap-client -m post coap://localhost:5683/api/v1/%s/telemetry -f-", credentials.getCredentialsId())); } 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 85aab629d8..82d380056b 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 @@ -103,6 +103,8 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Tue, 27 Jun 2023 17:30:56 +0300 Subject: [PATCH 020/168] Test rule node script function with selected event implementation --- .../script/node-script-test.service.ts | 52 ++++++++----------- .../components/details-panel.component.ts | 10 +++- .../components/event/event-table-config.ts | 26 ++++++++-- .../components/event/event-table.component.ts | 34 ++++++++++-- .../rulechain/rule-node-config.component.ts | 8 +++ .../rule-node-details.component.html | 3 +- .../rulechain/rule-node-details.component.ts | 4 ++ .../rulechain/rulechain-page.component.html | 13 +++-- .../rulechain/rulechain-page.component.ts | 16 +++++- .../src/app/shared/models/rule-node.models.ts | 35 ++++++++++--- .../assets/locale/locale.constant-en_US.json | 11 +++- 11 files changed, 160 insertions(+), 52 deletions(-) diff --git a/ui-ngx/src/app/core/services/script/node-script-test.service.ts b/ui-ngx/src/app/core/services/script/node-script-test.service.ts index 1a33275111..c3e0da73ad 100644 --- a/ui-ngx/src/app/core/services/script/node-script-test.service.ts +++ b/ui-ngx/src/app/core/services/script/node-script-test.service.ts @@ -23,8 +23,8 @@ import { NodeScriptTestDialogComponent, NodeScriptTestDialogData } from '@shared/components/dialog/node-script-test-dialog.component'; -import { sortObjectKeys } from '@core/utils'; import { ScriptLanguage } from '@shared/models/rule-node.models'; +import { DebugRuleNodeEventBody } from '@shared/models/event.models'; @Injectable({ providedIn: 'root' @@ -37,56 +37,50 @@ export class NodeScriptTestService { testNodeScript(script: string, scriptType: string, functionTitle: string, functionName: string, argNames: string[], ruleNodeId: string, helpId?: string, - scriptLang?: ScriptLanguage): Observable { - if (ruleNodeId) { + scriptLang?: ScriptLanguage, debugEventBody?: DebugRuleNodeEventBody): Observable { + if (ruleNodeId && !debugEventBody) { return this.ruleChainService.getLatestRuleNodeDebugInput(ruleNodeId).pipe( switchMap((debugIn) => { - let msg: any; - let metadata: {[key: string]: string}; - let msgType: string; - if (debugIn) { - if (debugIn.data) { - try { - msg = JSON.parse(debugIn.data); - } catch (e) {} - } - if (debugIn.metadata) { - try { - metadata = JSON.parse(debugIn.metadata); - } catch (e) {} - } - msgType = debugIn.msgType; - } return this.openTestScriptDialog(script, scriptType, functionTitle, - functionName, argNames, msg, metadata, msgType, helpId, scriptLang); + functionName, argNames, debugIn, helpId, scriptLang); }) ); } else { return this.openTestScriptDialog(script, scriptType, functionTitle, - functionName, argNames, null, null, null, helpId, scriptLang); + functionName, argNames, debugEventBody, helpId, scriptLang); } } - private openTestScriptDialog(script: string, scriptType: string, - functionTitle: string, functionName: string, argNames: string[], - msg?: any, metadata?: {[key: string]: string}, msgType?: string, helpId?: string, + private openTestScriptDialog(script: string, scriptType: string, functionTitle: string, functionName: string, + argNames: string[], eventBody: DebugRuleNodeEventBody, helpId?: string, scriptLang?: ScriptLanguage): Observable { - if (!msg) { + let msg: any; + let metadata: {[key: string]: string}; + let msgType: string; + if (eventBody && eventBody.data) { + try { + msg = JSON.parse(eventBody.data); + } catch (e) {} + } else { msg = { temperature: 22.4, humidity: 78 }; } - if (!metadata) { + if (eventBody && eventBody.metadata) { + try { + metadata = JSON.parse(eventBody.metadata); + } catch (e) {} + } else { metadata = { deviceName: 'Test Device', deviceType: 'default', ts: new Date().getTime() + '' }; - } else { - metadata = sortObjectKeys(metadata); } - if (!msgType) { + if (eventBody && eventBody.msgType) { + msgType = eventBody.msgType; + } else { msgType = 'POST_TELEMETRY_REQUEST'; } return this.dialog.open(NodeScriptTestDialogComponent, diff --git a/ui-ngx/src/app/modules/home/components/details-panel.component.ts b/ui-ngx/src/app/modules/home/components/details-panel.component.ts index b2f8a059f3..ac8d6e3bc3 100644 --- a/ui-ngx/src/app/modules/home/components/details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/details-panel.component.ts @@ -15,7 +15,6 @@ /// import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, @@ -56,7 +55,12 @@ export class DetailsPanelComponent extends PageComponent implements OnDestroy { } this.theFormValue = value; if (this.theFormValue !== null) { - this.formSubscription = this.theFormValue.valueChanges.subscribe(() => this.cd.detectChanges()); + this.formSubscription = this.theFormValue.valueChanges.subscribe(() => { + if (this.isReadOnly) { + this.switchToFirstTab.emit(); + } + this.cd.detectChanges() + }); } } } @@ -73,6 +77,8 @@ export class DetailsPanelComponent extends PageComponent implements OnDestroy { applyDetails = new EventEmitter(); @Output() closeSearch = new EventEmitter(); + @Output() + switchToFirstTab = new EventEmitter() isEditValue = false; showSearchPane = false; diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index a9816a130c..51bd99fde0 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -21,7 +21,7 @@ import { EntityTableColumn, EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { DebugEventType, Event, EventType, FilterEventBody } from '@shared/models/event.models'; +import { DebugEventType, DebugRuleNodeEventBody, Event, EventType, FilterEventBody } from '@shared/models/event.models'; import { TimePageLink } from '@shared/models/page/page-link'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; @@ -30,7 +30,7 @@ import { EntityId } from '@shared/models/id/entity-id'; import { EventService } from '@app/core/http/event.service'; import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component'; import { EntityTypeResource } from '@shared/models/entity-type.models'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; import { Direction } from '@shared/models/page/sort-order'; import { DialogService } from '@core/services/dialog.service'; @@ -49,6 +49,8 @@ import { EventFilterPanelData, FilterEntityColumn } from '@home/components/event/event-filter-panel.component'; +import { NodeScriptTestService } from '@core/services/script/node-script-test.service'; +import { ruleNodeClazzFunctionNameTranslations } from '@shared/models/rule-node.models'; export class EventTableConfig extends EntityTableConfig { @@ -72,6 +74,8 @@ export class EventTableConfig extends EntityTableConfig { eventTypes: Array; + debugEventSelectedSubject = new BehaviorSubject(null); + constructor(private eventService: EventService, private dialogService: DialogService, private translate: TranslateService, @@ -84,7 +88,11 @@ export class EventTableConfig extends EntityTableConfig { private debugEventTypes: Array = null, private overlay: Overlay, private viewContainerRef: ViewContainerRef, - private cd: ChangeDetectorRef) { + private cd: ChangeDetectorRef, + private nodeScriptTestService: NodeScriptTestService, + private isRuleNodeDebugModeEnabled: boolean, + private editingRuleNodeHasScript: boolean, + private rulenodeClazz: string) { super(); this.loadDataOnInit = false; this.tableTitle = ''; @@ -317,6 +325,18 @@ export class EventTableConfig extends EntityTableConfig { onAction: ($event, entity) => this.showContent($event, entity.body.error, 'event.error') }, + '48px'), + new EntityActionTableColumn('test', '', + { + name: this.translate.instant('rulenode.test-function', + {function: this.translate.instant(ruleNodeClazzFunctionNameTranslations[this.rulenodeClazz])}), + icon: 'bug_report', + isEnabled: (entity) => this.isRuleNodeDebugModeEnabled && entity.body.type === 'IN' && + this.editingRuleNodeHasScript, + onAction: ($event, entity) => { + this.debugEventSelectedSubject.next(entity.body); + } + }, '48px') ); break; diff --git a/ui-ngx/src/app/modules/home/components/event/event-table.component.ts b/ui-ngx/src/app/modules/home/components/event/event-table.component.ts index fbdd0a07da..4781b4811d 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table.component.ts @@ -17,10 +17,10 @@ import { AfterViewInit, ChangeDetectorRef, - Component, + Component, EventEmitter, Input, OnDestroy, - OnInit, + OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core'; @@ -32,9 +32,10 @@ import { EntitiesTableComponent } from '@home/components/entity/entities-table.c import { EventTableConfig } from './event-table-config'; import { EventService } from '@core/http/event.service'; import { DialogService } from '@core/services/dialog.service'; -import { DebugEventType, EventType } from '@shared/models/event.models'; +import { DebugEventType, DebugRuleNodeEventBody, EventType } from '@shared/models/event.models'; import { Overlay } from '@angular/cdk/overlay'; import { Subscription } from 'rxjs'; +import { NodeScriptTestService } from '@core/services/script/node-script-test.service'; @Component({ selector: 'tb-event-table', @@ -83,6 +84,18 @@ export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy { } } + @Input() + isRuleNodeDebugModeEnabled: boolean; + + @Input() + editingRuleNodeHasScript: boolean; + + @Input() + rulenodeClazz: string; + + @Output() + debugEventSelected = new EventEmitter(null); + @ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent; eventTableConfig: EventTableConfig; @@ -96,7 +109,8 @@ export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy { private dialog: MatDialog, private overlay: Overlay, private viewContainerRef: ViewContainerRef, - private cd: ChangeDetectorRef) { + private cd: ChangeDetectorRef, + private nodeScriptTestService: NodeScriptTestService) { } ngOnInit() { @@ -114,8 +128,18 @@ export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy { this.debugEventTypes, this.overlay, this.viewContainerRef, - this.cd + this.cd, + this.nodeScriptTestService, + this.isRuleNodeDebugModeEnabled, + this.editingRuleNodeHasScript, + this.rulenodeClazz ); + + this.eventTableConfig.debugEventSelectedSubject.subscribe((debugEventBody: DebugRuleNodeEventBody) => { + if (debugEventBody) { + this.debugEventSelected.emit(debugEventBody); + } + }) } ngAfterViewInit() { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts index 73f0d5e14a..788a495607 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts @@ -38,6 +38,7 @@ import { TranslateService } from '@ngx-translate/core'; import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; import { deepClone } from '@core/utils'; import { RuleChainType } from '@shared/models/rule-chain.models'; +import { DebugRuleNodeEventBody } from '@shared/models/event.models'; @Component({ selector: 'tb-rule-node-config', @@ -92,6 +93,13 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On return this.nodeDefinitionValue; } + @Input() + set debugEventBody(debugEventBody: DebugRuleNodeEventBody) { + if (debugEventBody) { + this.definedConfigComponent?.testScript(debugEventBody); + } + } + definedDirectiveError: string; ruleNodeConfigFormGroup: UntypedFormGroup; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html index b2c73a4bdc..c215b0d21d 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html @@ -50,7 +50,8 @@ [ruleNodeId]="ruleNode.ruleNodeId?.id" [ruleChainId]="ruleChainId" [ruleChainType]="ruleChainType" - [nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition"> + [nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition" + [debugEventBody]="debugEventBody">
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts index dad1e715ec..455e70270c 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts @@ -27,6 +27,7 @@ import { RuleNodeConfigComponent } from './rule-node-config.component'; import { Router } from '@angular/router'; import { RuleChainType } from '@app/shared/models/rule-chain.models'; import { ComponentClusteringMode } from '@shared/models/component-descriptor.models'; +import { DebugRuleNodeEventBody } from '@shared/models/event.models'; @Component({ selector: 'tb-rule-node', @@ -55,6 +56,9 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O @Input() isAdd = false; + @Input() + debugEventBody: DebugRuleNodeEventBody; + ruleNodeType = RuleNodeType; entityType = EntityType; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html index 4cdaed942d..15695d9d87 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html @@ -100,7 +100,8 @@ (closeDetails)="onEditRuleNodeClosed()" (toggleDetailsEditMode)="onRevertRuleNodeEdit()" (applyDetails)="saveRuleNode()" - [theForm]="tbRuleNode.ruleNodeFormGroup"> + [theForm]="tbRuleNode.ruleNodeFormGroup" + (switchToFirstTab)="onSwitchToFirstTab()">
@@ -111,7 +112,8 @@ [ruleChainId]="ruleChain.id?.id" [ruleChainType]="ruleChainType" [isEdit]="true" - [isReadOnly]="false"> + [isReadOnly]="false" + [debugEventBody]="debugEventBody"> @@ -119,7 +121,12 @@ [defaultEventType]="debugEventTypes.DEBUG_RULE_NODE" [active]="eventsTab.isActive" [tenantId]="ruleChain.tenantId.id" - [entityId]="editingRuleNode.ruleNodeId"> + [entityId]="editingRuleNode.ruleNodeId" + [isRuleNodeDebugModeEnabled]="editingRuleNode?.debugMode" + [editingRuleNodeHasScript]="editingRuleNodeHasScript" + [rulenodeClazz]="editingRuleNode.component.clazz" + (debugEventSelected)="onDebugEventSelected($event)"> +
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts index d9c6b1b3bb..ff9d9060c8 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts @@ -87,7 +87,7 @@ import { DialogComponent } from '@shared/components/dialog.component'; import { MatMenuTrigger } from '@angular/material/menu'; import { ItemBufferService, RuleNodeConnection } from '@core/services/item-buffer.service'; import { Hotkey } from 'angular2-hotkeys'; -import { DebugEventType, EventType } from '@shared/models/event.models'; +import { DebugEventType, DebugRuleNodeEventBody, EventType } from '@shared/models/event.models'; import { MatMiniFabButton } from '@angular/material/button'; import { TbPopoverService } from '@shared/components/popover.service'; import { VersionControlComponent } from '@home/components/vc/version-control.component'; @@ -153,6 +153,8 @@ export class RuleChainPageComponent extends PageComponent editingRuleNodeAllowCustomLabels = false; editingRuleNodeLinkLabels: {[label: string]: LinkLabel}; editingRuleNodeSourceRuleChainId: string; + debugEventBody: DebugRuleNodeEventBody; + editingRuleNodeHasScript: boolean = false; @ViewChild('tbRuleNode') ruleNodeComponent: RuleNodeDetailsComponent; @ViewChild('tbRuleNodeLink') ruleNodeLinkComponent: RuleNodeLinkComponent; @@ -1110,6 +1112,7 @@ export class RuleChainPageComponent extends PageComponent this.isEditingRuleNode = true; this.editingRuleNodeIndex = this.ruleChainModel.nodes.indexOf(node); this.editingRuleNode = deepClone(node, ['component']); + this.editingRuleNodeHasScript = this.editingRuleNode.configuration.hasOwnProperty('scriptLang'); setTimeout(() => { this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); }, 0); @@ -1258,6 +1261,7 @@ export class RuleChainPageComponent extends PageComponent onEditRuleNodeClosed() { this.editingRuleNode = null; this.isEditingRuleNode = false; + this.debugEventBody = null; } onEditRuleNodeLinkClosed() { @@ -1277,6 +1281,16 @@ export class RuleChainPageComponent extends PageComponent this.editingRuleNodeLink = deepClone(edge); } + onDebugEventSelected(debugEventBody: DebugRuleNodeEventBody) { + if (debugEventBody) { + this.debugEventBody = debugEventBody; + } + } + + onSwitchToFirstTab() { + this.selectedRuleNodeTabIndex = 0; + } + saveRuleNode() { this.ruleNodeComponent.validate(); if (this.ruleNodeComponent.ruleNodeFormGroup.valid) { diff --git a/ui-ngx/src/app/shared/models/rule-node.models.ts b/ui-ngx/src/app/shared/models/rule-node.models.ts index d4688546d7..84ea8ddd3e 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -26,6 +26,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { AbstractControl, UntypedFormGroup } from '@angular/forms'; import { RuleChainType } from '@shared/models/rule-chain.models'; +import { DebugRuleNodeEventBody } from '@shared/models/event.models'; export interface RuleNodeConfiguration { [key: string]: any; @@ -75,6 +76,7 @@ export interface IRuleNodeConfigurationComponent { configuration: RuleNodeConfiguration; configurationChanged: Observable; validate(); + testScript? (debugEventBody: DebugRuleNodeEventBody); [key: string]: any; } @@ -427,13 +429,22 @@ export const messageTypeNames = new Map( export const ruleChainNodeClazz = 'org.thingsboard.rule.engine.flow.TbRuleChainInputNode'; export const outputNodeClazz = 'org.thingsboard.rule.engine.flow.TbRuleChainOutputNode'; +export enum RuleNodeClazz { + TbJsFilterNode = 'org.thingsboard.rule.engine.filter.TbJsFilterNode', + TbLogNode = 'org.thingsboard.rule.engine.action.TbLogNode', + TbJsSwitchNode = 'org.thingsboard.rule.engine.filter.TbJsSwitchNode', + TbClearAlarmNode = 'org.thingsboard.rule.engine.action.TbClearAlarmNode', + TbCreateAlarmNode = 'org.thingsboard.rule.engine.action.TbCreateAlarmNode', + TbTransformMsgNode = 'org.thingsboard.rule.engine.transform.TbTransformMsgNode', + TbMsgGeneratorNode = 'org.thingsboard.rule.engine.debug.TbMsgGeneratorNode' +} const ruleNodeClazzHelpLinkMap = { 'org.thingsboard.rule.engine.filter.TbCheckRelationNode': 'ruleNodeCheckRelation', 'org.thingsboard.rule.engine.filter.TbCheckMessageNode': 'ruleNodeCheckExistenceFields', 'org.thingsboard.rule.engine.geo.TbGpsGeofencingFilterNode': 'ruleNodeGpsGeofencingFilter', - 'org.thingsboard.rule.engine.filter.TbJsFilterNode': 'ruleNodeJsFilter', - 'org.thingsboard.rule.engine.filter.TbJsSwitchNode': 'ruleNodeJsSwitch', + [RuleNodeClazz.TbJsFilterNode]: 'ruleNodeJsFilter', + [RuleNodeClazz.TbJsSwitchNode]: 'ruleNodeJsSwitch', 'org.thingsboard.rule.engine.filter.TbAssetTypeSwitchNode': 'ruleNodeAssetProfileSwitch', 'org.thingsboard.rule.engine.filter.TbDeviceTypeSwitchNode': 'ruleNodeDeviceProfileSwitch', 'org.thingsboard.rule.engine.filter.TbCheckAlarmStatusNode': 'ruleNodeCheckAlarmStatus', @@ -452,18 +463,18 @@ const ruleNodeClazzHelpLinkMap = { 'org.thingsboard.rule.engine.metadata.TbGetTenantDetailsNode': 'ruleNodeTenantDetails', 'org.thingsboard.rule.engine.metadata.CalculateDeltaNode': 'ruleNodeCalculateDelta', 'org.thingsboard.rule.engine.transform.TbChangeOriginatorNode': 'ruleNodeChangeOriginator', - 'org.thingsboard.rule.engine.transform.TbTransformMsgNode': 'ruleNodeTransformMsg', + [RuleNodeClazz.TbTransformMsgNode]: 'ruleNodeTransformMsg', 'org.thingsboard.rule.engine.mail.TbMsgToEmailNode': 'ruleNodeMsgToEmail', 'org.thingsboard.rule.engine.action.TbAssignToCustomerNode': 'ruleNodeAssignToCustomer', 'org.thingsboard.rule.engine.action.TbUnassignFromCustomerNode': 'ruleNodeUnassignFromCustomer', - 'org.thingsboard.rule.engine.action.TbClearAlarmNode': 'ruleNodeClearAlarm', - 'org.thingsboard.rule.engine.action.TbCreateAlarmNode': 'ruleNodeCreateAlarm', + [RuleNodeClazz.TbClearAlarmNode]: 'ruleNodeClearAlarm', + [RuleNodeClazz.TbCreateAlarmNode]: 'ruleNodeCreateAlarm', 'org.thingsboard.rule.engine.action.TbCreateRelationNode': 'ruleNodeCreateRelation', 'org.thingsboard.rule.engine.action.TbDeleteRelationNode': 'ruleNodeDeleteRelation', 'org.thingsboard.rule.engine.delay.TbMsgDelayNode': 'ruleNodeMsgDelay', - 'org.thingsboard.rule.engine.debug.TbMsgGeneratorNode': 'ruleNodeMsgGenerator', + [RuleNodeClazz.TbMsgGeneratorNode]: 'ruleNodeMsgGenerator', 'org.thingsboard.rule.engine.geo.TbGpsGeofencingActionNode': 'ruleNodeGpsGeofencingEvents', - 'org.thingsboard.rule.engine.action.TbLogNode': 'ruleNodeLog', + [RuleNodeClazz.TbLogNode]: 'ruleNodeLog', 'org.thingsboard.rule.engine.rpc.TbSendRPCReplyNode': 'ruleNodeRpcCallReply', 'org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode': 'ruleNodeRpcCallRequest', 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode': 'ruleNodeSaveAttributes', @@ -499,3 +510,13 @@ export function getRuleNodeHelpLink(component: RuleNodeComponentDescriptor): str } return 'ruleEngine'; } + +export const ruleNodeClazzFunctionNameTranslations = { + [RuleNodeClazz.TbJsFilterNode]: 'rulenode.function-name.filter', + [RuleNodeClazz.TbLogNode]: 'rulenode.function-name.to-string', + [RuleNodeClazz.TbJsSwitchNode]: 'rulenode.function-name.switch', + [RuleNodeClazz.TbClearAlarmNode]: 'rulenode.function-name.details', + [RuleNodeClazz.TbCreateAlarmNode]: 'rulenode.function-name.details', + [RuleNodeClazz.TbTransformMsgNode]: 'rulenode.function-name.transform', + [RuleNodeClazz.TbMsgGeneratorNode]: 'rulenode.function-name.generate' +} 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 341408a615..af513bbaf6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3464,7 +3464,16 @@ "output": "Output", "test": "Test", "help": "Help", - "reset-debug-mode": "Reset debug mode in all nodes" + "reset-debug-mode": "Reset debug mode in all nodes", + "test-function": "Test '{{function}}' function with this message", + "function-name": { + "filter": "Filter", + "to-string": "ToString", + "details": "Details", + "generate": "Generate", + "switch": "Switch", + "transform": "Transform" + } }, "timezone": { "timezone": "Timezone", From 0c4c8353deccb765f1bf233576beff1887ac149a Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 28 Jun 2023 12:43:49 +0300 Subject: [PATCH 021/168] refactoring & added tests for alarm status filter node & entity and msg type switch nodes & check field presence --- .../actors/ruleChain/DefaultTbContext.java | 13 +- .../DefaultSystemDataLoaderService.java | 3 +- .../server/common/data/DataConstants.java | 1 + .../server/common/data/StringUtils.java | 2 +- .../AbstractGatewaySessionHandler.java | 2 +- .../external/TbAbstractExternalNode.java | 12 +- .../filter/TbAssetTypeSwitchNodeTest.java | 1 + .../filter/TbCheckAlarmStatusNodeTest.java | 3 +- .../engine/filter/TbCheckMessageNodeTest.java | 206 ++++++++++++++++++ .../filter/TbMsgTypeSwitchNodeTest.java | 97 +++++++++ .../TbOriginatorTypeSwitchNodeTest.java | 98 +++++++++ 11 files changed, 413 insertions(+), 25 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java 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 2d087de74e..f9d1940714 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 @@ -852,17 +852,10 @@ class DefaultTbContext implements TbContext { } private static String getFailureMessage(Throwable th) { - String failureMessage; - if (th != null) { - if (!StringUtils.isEmpty(th.getMessage())) { - failureMessage = th.getMessage(); - } else { - failureMessage = th.getClass().getSimpleName(); - } - } else { - failureMessage = null; + if (th == null) { + return null; } - return failureMessage; + return StringUtils.isNotEmpty(th.getMessage()) ? th.getMessage() : th.getClass().getSimpleName(); } private class SimpleTbQueueCallback implements TbQueueCallback { 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 7965824274..1087990c4e 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 @@ -114,13 +114,14 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import static org.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE; + @Service @Profile("install") @Slf4j public class DefaultSystemDataLoaderService implements SystemDataLoaderService { public static final String CUSTOMER_CRED = "customer"; - public static final String DEFAULT_DEVICE_TYPE = "default"; public static final String ACTIVITY_STATE = "active"; @Autowired diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 6e7341fa24..ed4431f445 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -65,6 +65,7 @@ public class DataConstants { public static final String PROVISION_KEY = "provisionDeviceKey"; public static final String PROVISION_SECRET = "provisionDeviceSecret"; + public static final String DEFAULT_DEVICE_TYPE = "default"; public static final String DEVICE_NAME = "deviceName"; public static final String DEVICE_TYPE = "deviceType"; public static final String CERT_PUB_KEY = "x509CertPubKey"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java index a7671f4327..6b70dfc09c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java @@ -42,7 +42,7 @@ public class StringUtils { } public static boolean isNotEmpty(String source) { - return source != null && !source.isEmpty(); + return !isEmpty(source); } public static boolean isNotBlank(String source) { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java index 95603e751c..5cd6ba9145 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java @@ -71,6 +71,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static org.springframework.util.ConcurrentReferenceHashMap.ReferenceType; +import static org.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE; import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN; import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG; @@ -85,7 +86,6 @@ import static org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMess @Slf4j public abstract class AbstractGatewaySessionHandler { - protected static final String DEFAULT_DEVICE_TYPE = "default"; private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; private static final String DEVICE_PROPERTY = "device"; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java index d9d25bc4cd..8402e87076 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java @@ -38,17 +38,9 @@ public abstract class TbAbstractExternalNode implements TbNode { protected void tellFailure(TbContext ctx, TbMsg tbMsg, Throwable t) { if (forceAck) { - if (t == null) { - ctx.enqueueForTellNext(tbMsg.copyWithNewCtx(), TbNodeConnectionType.FAILURE); - } else { - ctx.enqueueForTellFailure(tbMsg.copyWithNewCtx(), t); - } + ctx.enqueueForTellFailure(tbMsg.copyWithNewCtx(), t); } else { - if (t == null) { - ctx.tellNext(tbMsg, TbNodeConnectionType.FAILURE); - } else { - ctx.tellFailure(tbMsg, t); - } + ctx.tellFailure(tbMsg, t); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java index 3e4dd21fd2..7ef2f87845 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java @@ -124,4 +124,5 @@ class TbAssetTypeSwitchNodeTest { private TbMsg getTbMsg(EntityId entityId) { return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}", callback); } + } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java index 0677f0b0c9..4212af2ade 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java @@ -163,5 +163,4 @@ class TbCheckAlarmStatusNodeTest { return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), msgData); } - -} \ No newline at end of file +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java new file mode 100644 index 0000000000..dd25363aed --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java @@ -0,0 +1,206 @@ +/** + * 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.filter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +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.id.DeviceId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.List; +import java.util.UUID; + +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.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE; +import static org.thingsboard.server.common.data.DataConstants.DEVICE_NAME; +import static org.thingsboard.server.common.data.DataConstants.DEVICE_TYPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbCheckMessageNodeTest { + + private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); + private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); + private static final String EMPTY_DATA = "{}"; + private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, EMPTY_METADATA, EMPTY_DATA); + + private static TbCheckMessageNode node; + + private static TbContext ctx; + + @BeforeEach + void setUp() { + ctx = mock(TbContext.class); + node = new TbCheckMessageNode(); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenDefaultConfig_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var configuration = new TbCheckMessageNodeConfiguration().defaultConfiguration(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigWithoutCheckAllKeysAndWithEmptyLists_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var configuration = new TbCheckMessageNodeConfiguration().defaultConfiguration(); + configuration.setCheckAllKeys(false); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigWithCheckAllKeys_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var configuration = new TbCheckMessageNodeConfiguration().defaultConfiguration(); + configuration.setMessageNames(List.of("temperature-0")); + configuration.setMetadataNames(List.of("deviceName", "deviceType", "ts")); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration))); + + TbMsg tbMsg = getTbMsg(); + + // WHEN + node.onMsg(ctx, tbMsg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(tbMsg); + } + + @Test + void givenCustomConfigWithCheckAllKeys_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var configuration = new TbCheckMessageNodeConfiguration().defaultConfiguration(); + configuration.setMessageNames(List.of("temperature-0", "temperature-1")); + configuration.setMetadataNames(List.of("deviceName", "deviceType", "ts")); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration))); + + TbMsg tbMsg = getTbMsg(); + + // WHEN + node.onMsg(ctx, tbMsg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(tbMsg); + } + + @Test + void givenCustomConfigWithoutCheckAllKeys_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var configuration = new TbCheckMessageNodeConfiguration().defaultConfiguration(); + configuration.setMessageNames(List.of("temperature-0", "temperature-1")); + configuration.setCheckAllKeys(false); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration))); + + TbMsg tbMsg = getTbMsg(); + + // WHEN + node.onMsg(ctx, tbMsg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(tbMsg); + } + + @Test + void givenCustomConfigWithoutCheckAllKeysAndEmptyMsg_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var configuration = new TbCheckMessageNodeConfiguration().defaultConfiguration(); + configuration.setMessageNames(List.of("temperature-0", "temperature-1")); + configuration.setCheckAllKeys(false); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration))); + + TbMsg tbMsg = getTbMsg(true); + + // WHEN + node.onMsg(ctx, tbMsg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(tbMsg); + } + + private TbMsg getTbMsg() { + return getTbMsg(false); + } + + private TbMsg getTbMsg(boolean emptyData) { + String data = emptyData ? EMPTY_DATA : "{\"temperature-0\": 25}"; + var metadata = new TbMsgMetaData(); + metadata.putValue(DEVICE_NAME, "Test Device"); + metadata.putValue(DEVICE_TYPE, DEFAULT_DEVICE_TYPE); + metadata.putValue("ts", String.valueOf(System.currentTimeMillis())); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, metadata, data); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java new file mode 100644 index 0000000000..51155688e6 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java @@ -0,0 +1,97 @@ +/** + * 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.filter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +class TbMsgTypeSwitchNodeTest { + + private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); + private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); + private static final String EMPTY_DATA = "{}"; + + private static TbMsgTypeSwitchNode node; + + private static TbContext ctx; + + @BeforeEach + void setUp() { + ctx = mock(TbContext.class); + node = new TbMsgTypeSwitchNode(); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenAllTypes_whenOnMsg_then_allTypesSupported() throws TbNodeException { + // GIVEN + List tbMsgList = new ArrayList<>(); + var tbMsgTypes = TbMsgType.values(); + for (var msgType : tbMsgTypes) { + tbMsgList.add(getTbMsg(msgType)); + } + + // WHEN + for (TbMsg tbMsg : tbMsgList) { + node.onMsg(ctx, tbMsg); + } + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor nodeConnectionCapture = ArgumentCaptor.forClass(String.class); + verify(ctx, times(tbMsgList.size())).tellNext(newMsgCaptor.capture(), nodeConnectionCapture.capture()); + verify(ctx, never()).tellFailure(any(), any()); + var resultMsgs = newMsgCaptor.getAllValues(); + var resultNodeConnections = nodeConnectionCapture.getAllValues(); + for (int i = 0; i < resultMsgs.size(); i++) { + var msg = resultMsgs.get(i); + assertThat(msg).isNotNull(); + assertThat(msg.getType()).isNotNull(); + assertThat(msg).isSameAs(tbMsgList.get(i)); + // todo add additional validation that types like ALARM or PROVISION returns OTHER for backward-compatibility. + assertThat(resultNodeConnections.get(i)) + .isEqualTo(TbMsgType.getRuleNodeConnection(msg.getType())); + } + } + + private TbMsg getTbMsg(TbMsgType msgType) { + return TbMsg.newMsg(msgType.name(), DEVICE_ID, EMPTY_METADATA, EMPTY_DATA); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java new file mode 100644 index 0000000000..09e67e45ef --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java @@ -0,0 +1,98 @@ +/** + * 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.filter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +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.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbOriginatorTypeSwitchNodeTest { + + private static final UUID RANDOM_UUID = UUID.randomUUID(); + private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); + private static final String EMPTY_DATA = "{}"; + + private static TbOriginatorTypeSwitchNode node; + + private static TbContext ctx; + + @BeforeEach + void setUp() { + ctx = mock(TbContext.class); + node = new TbOriginatorTypeSwitchNode(); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenAllTypes_whenOnMsg_then_allTypesSupported() throws TbNodeException { + // GIVEN + List tbMsgList = new ArrayList<>(); + var entityTypes = EntityType.values(); + for (var entityType : entityTypes) { + var entityId = EntityIdFactory.getByTypeAndUuid(entityType, RANDOM_UUID); + tbMsgList.add(getTbMsg(entityId)); + } + + // WHEN + for (TbMsg tbMsg : tbMsgList) { + node.onMsg(ctx, tbMsg); + } + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor nodeConnectionCapture = ArgumentCaptor.forClass(String.class); + verify(ctx, times(tbMsgList.size())).tellNext(newMsgCaptor.capture(), nodeConnectionCapture.capture()); + verify(ctx, never()).tellFailure(any(), any()); + var resultMsgs = newMsgCaptor.getAllValues(); + var resultNodeConnections = nodeConnectionCapture.getAllValues(); + for (int i = 0; i < resultMsgs.size(); i++) { + var msg = resultMsgs.get(i); + assertThat(msg).isNotNull(); + assertThat(msg).isSameAs(tbMsgList.get(i)); + assertThat(resultNodeConnections.get(i)) + .isEqualTo(msg.getOriginator().getEntityType().getNormalName()); + } + } + + private TbMsg getTbMsg(EntityId entityId) { + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, EMPTY_METADATA, EMPTY_DATA); + } + +} From 27b1d3f5d56a84ac1c90fd59004eba49c96a6459 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 28 Jun 2023 12:49:17 +0300 Subject: [PATCH 022/168] fix typo in NashornJsInvokeServiceTest --- .../server/service/script/NashornJsInvokeServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java index 8d7de23303..5cd54a24eb 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java @@ -122,7 +122,7 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest { } private String invokeScript(UUID scriptId, String msg) throws ExecutionException, InterruptedException { - return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.getRuleNodeConnection()).get().toString(); + return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.name()).get().toString(); } } From f01b2d6595dbf997da1cf4b4004560481fec19a3 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 28 Jun 2023 12:59:27 +0300 Subject: [PATCH 023/168] fix typo in TbelInvokeServiceTest --- .../server/service/script/TbelInvokeServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java index 2895c97c90..62af9cb16e 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java @@ -217,7 +217,7 @@ class TbelInvokeServiceTest extends AbstractControllerTest { private String invokeScript(UUID scriptId, String str) throws ExecutionException, InterruptedException { var msg = JacksonUtil.fromString(str, Map.class); - return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.getRuleNodeConnection()).get().toString(); + return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.name()).get().toString(); } } From 8beb81cf8d5ff1d7fe652c3bd1bcb61a6c61402c Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 30 Jun 2023 13:35:03 +0300 Subject: [PATCH 024/168] added tests for check relation presence node & test for TbMsgType and ActionType & refactoring --- .../device/DeviceProvisionServiceImpl.java | 2 +- .../processor/device/DeviceEdgeProcessor.java | 3 +- .../server/common/data/msg/TbMsgType.java | 4 +- .../common/data/audit/ActionTypeTest.java | 63 ++++ .../server/common/data/msg/TbMsgTypeTest.java | 70 +++++ .../engine/filter/TbCheckRelationNode.java | 10 +- .../engine/filter/TbMsgTypeSwitchNode.java | 2 +- .../filter/TbAssetTypeSwitchNodeTest.java | 27 +- .../filter/TbCheckAlarmStatusNodeTest.java | 20 +- .../engine/filter/TbCheckMessageNodeTest.java | 4 +- .../filter/TbCheckRelationNodeTest.java | 297 ++++++++++++++++++ .../filter/TbDeviceTypeSwitchNodeTest.java | 22 +- .../engine/filter/TbJsFilterNodeTest.java | 4 +- .../engine/filter/TbJsSwitchNodeTest.java | 4 +- .../filter/TbMsgTypeSwitchNodeTest.java | 7 +- .../TbOriginatorTypeSwitchNodeTest.java | 4 +- .../TbGetCustomerAttributeNodeTest.java | 6 +- .../TbGetCustomerDetailsNodeTest.java | 2 +- .../TbGetOriginatorFieldsNodeTest.java | 8 +- .../TbGetRelatedAttributeNodeTest.java | 2 +- .../TbGetTenantAttributeNodeTest.java | 4 +- .../metadata/TbGetTenantDetailsNodeTest.java | 2 +- 22 files changed, 498 insertions(+), 69 deletions(-) create mode 100644 common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java create mode 100644 common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 3bed8a1905..d5614c2193 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -273,7 +273,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { TbMsg msg = TbMsg.newMsg(ENTITY_CREATED.name(), device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); sendToRuleEngine(device.getTenantId(), msg, null); } catch (JsonProcessingException | IllegalArgumentException e) { - log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), ENTITY_CREATED, e); + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), ENTITY_CREATED.name(), e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index 3848828522..49f2e0ead2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -63,6 +63,7 @@ import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import java.util.UUID; import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.TO_SERVER_RPC_REQUEST; @Component @Slf4j @@ -218,7 +219,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { ObjectNode data = JacksonUtil.newObjectNode(); data.put("method", deviceRpcCallMsg.getRequestMsg().getMethod()); data.put("params", deviceRpcCallMsg.getRequestMsg().getParams()); - TbMsg tbMsg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST.name(), deviceId, null, metaData, + TbMsg tbMsg = TbMsg.newMsg(TO_SERVER_RPC_REQUEST.name(), deviceId, null, metaData, TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(data)); tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override 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 a974d8adbd..1872fd676f 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 @@ -42,7 +42,7 @@ public enum TbMsgType { ALARM(null), ALARM_ACK("Alarm Acknowledged"), ALARM_CLEAR("Alarm Cleared"), - ALARM_DELETE("Alarm Deleted"), + ALARM_DELETE(null), ALARM_ASSIGNED("Alarm Assigned"), ALARM_UNASSIGNED("Alarm Unassigned"), COMMENT_CREATED("Comment Created"), @@ -78,7 +78,7 @@ public enum TbMsgType { this.ruleNodeConnection = ruleNodeConnection; } - public static String getRuleNodeConnection(String msgType) { + public static String getRuleNodeConnectionOrElseOther(String msgType) { if (msgType == null) { return TbNodeConnectionType.OTHER; } else { diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java new file mode 100644 index 0000000000..b76b0fc2b7 --- /dev/null +++ b/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java @@ -0,0 +1,63 @@ +/** + * 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.common.data.audit; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.thingsboard.server.common.data.audit.ActionType.ACTIVATED; +import static org.thingsboard.server.common.data.audit.ActionType.ATTRIBUTES_READ; +import static org.thingsboard.server.common.data.audit.ActionType.CREDENTIALS_READ; +import static org.thingsboard.server.common.data.audit.ActionType.CREDENTIALS_UPDATED; +import static org.thingsboard.server.common.data.audit.ActionType.DELETED_COMMENT; +import static org.thingsboard.server.common.data.audit.ActionType.LOCKOUT; +import static org.thingsboard.server.common.data.audit.ActionType.LOGIN; +import static org.thingsboard.server.common.data.audit.ActionType.LOGOUT; +import static org.thingsboard.server.common.data.audit.ActionType.RPC_CALL; +import static org.thingsboard.server.common.data.audit.ActionType.SMS_SENT; +import static org.thingsboard.server.common.data.audit.ActionType.SUSPENDED; + +class ActionTypeTest { + + private static final List typesWithNullRuleEngineMsgType = List.of( + RPC_CALL, + CREDENTIALS_UPDATED, + ACTIVATED, + SUSPENDED, + CREDENTIALS_READ, + ATTRIBUTES_READ, + LOGIN, + LOGOUT, + LOCKOUT, + DELETED_COMMENT, + SMS_SENT + ); + + // backward-compatibility tests + + @Test + void getRuleEngineMsgTypeTest() { + var types = ActionType.values(); + for (var type : types) { + if (typesWithNullRuleEngineMsgType.contains(type)) { + assertThat(type.getRuleEngineMsgType()).isEmpty(); + } + } + } + +} 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 new file mode 100644 index 0000000000..c1f9dffd17 --- /dev/null +++ b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java @@ -0,0 +1,70 @@ +/** + * 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.common.data.msg; + +import org.junit.jupiter.api.Test; + +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.ENTITY_ASSIGNED_TO_EDGE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UNASSIGNED_FROM_EDGE; +import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_FAILURE; +import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_SUCCESS; + +class TbMsgTypeTest { + + private static final List typesWithNullRuleNodeConnection = List.of( + ALARM, + ALARM_DELETE, + ENTITY_ASSIGNED_TO_EDGE, + ENTITY_UNASSIGNED_FROM_EDGE, + PROVISION_FAILURE, + PROVISION_SUCCESS + ); + + + // backward-compatibility tests + + @Test + void getRuleNodeConnectionsTest() { + var tbMsgTypes = TbMsgType.values(); + for (var type : tbMsgTypes) { + if (typesWithNullRuleNodeConnection.contains(type)) { + assertThat(type.getRuleNodeConnection()).isNull(); + } + } + } + + @Test + void getRuleNodeConnectionOrElseOtherTest() { + assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(null)) + .isEqualTo(TbNodeConnectionType.OTHER); + var tbMsgTypes = TbMsgType.values(); + for (var type : tbMsgTypes) { + if (typesWithNullRuleNodeConnection.contains(type)) { + assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(type.name())) + .isEqualTo(TbNodeConnectionType.OTHER); + } else { + assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(type.name())).isNotNull() + .isNotEqualTo(TbNodeConnectionType.OTHER); + } + } + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index 3dfa6cb233..2186378527 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -22,6 +22,7 @@ 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.server.common.data.StringUtils; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; @@ -62,6 +63,9 @@ public class TbCheckRelationNode implements TbNode { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbCheckRelationNodeConfiguration.class); if (config.isCheckForSingleEntity()) { + if (StringUtils.isEmpty(config.getEntityType()) || StringUtils.isEmpty(config.getEntityId())) { + throw new TbNodeException("Entity should be specified!"); + } this.singleEntityId = EntityIdFactory.getByTypeAndId(config.getEntityType(), config.getEntityId()); ctx.checkTenantEntity(singleEntityId); } @@ -90,9 +94,9 @@ public class TbCheckRelationNode implements TbNode { } private ListenableFuture processList(TbContext ctx, TbMsg msg) { - ListenableFuture> relationListFuture = EntitySearchDirection.FROM.name().equals(config.getDirection()) ? ctx.getRelationService() - .findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON) : ctx.getRelationService() - .findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON); + ListenableFuture> relationListFuture = EntitySearchDirection.FROM.name().equals(config.getDirection()) ? + ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON) : + ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON); return Futures.transformAsync(relationListFuture, this::isEmptyList, ctx.getDbCallbackExecutor()); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index bd0d5d5160..2121e0c5fa 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -50,7 +50,7 @@ public class TbMsgTypeSwitchNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ctx.tellNext(msg, TbMsgType.getRuleNodeConnection(msg.getType())); + ctx.tellNext(msg, TbMsgType.getRuleNodeConnectionOrElseOther(msg.getType())); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java index 7ef2f87845..a6b2433df4 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java @@ -50,34 +50,33 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R class TbAssetTypeSwitchNodeTest { - TenantId tenantId; - AssetId assetId; - AssetId assetIdDeleted; - AssetProfile assetProfile; - TbContext ctx; - TbAssetTypeSwitchNode node; - EmptyNodeConfiguration config; - TbMsgCallback callback; - RuleEngineAssetProfileCache assetProfileCache; + private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); + private static final String EMPTY_DATA = "{}"; + + private AssetId assetId; + private AssetId assetIdDeleted; + private TbContext ctx; + private TbAssetTypeSwitchNode node; + private TbMsgCallback callback; @BeforeEach void setUp() throws TbNodeException { - tenantId = new TenantId(UUID.randomUUID()); + TenantId tenantId = new TenantId(UUID.randomUUID()); assetId = new AssetId(UUID.randomUUID()); assetIdDeleted = new AssetId(UUID.randomUUID()); - assetProfile = new AssetProfile(); + AssetProfile assetProfile = new AssetProfile(); assetProfile.setTenantId(tenantId); assetProfile.setName("TestAssetProfile"); //node - config = new EmptyNodeConfiguration(); + EmptyNodeConfiguration config = new EmptyNodeConfiguration(); node = new TbAssetTypeSwitchNode(); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); //init mock ctx = mock(TbContext.class); - assetProfileCache = mock(RuleEngineAssetProfileCache.class); + RuleEngineAssetProfileCache assetProfileCache = mock(RuleEngineAssetProfileCache.class); callback = mock(TbMsgCallback.class); when(ctx.getTenantId()).thenReturn(tenantId); @@ -122,7 +121,7 @@ class TbAssetTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}", callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, EMPTY_METADATA, EMPTY_DATA, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java index 4212af2ade..a794b96620 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java @@ -29,7 +29,6 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; @@ -53,14 +52,15 @@ class TbCheckAlarmStatusNodeTest { private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); private static final AlarmId ALARM_ID = new AlarmId(UUID.randomUUID()); private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); + private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); - private static TbCheckAlarmStatusNode node; + private TbCheckAlarmStatusNode node; - private static TbContext ctx; - private static RuleEngineAlarmService alarmService; + private TbContext ctx; + private RuleEngineAlarmService alarmService; @BeforeEach - public void setUp() throws TbNodeException { + void setUp() throws TbNodeException { var config = new TbCheckAlarmStatusNodeConfig().defaultConfiguration(); ctx = mock(TbContext.class); @@ -88,7 +88,7 @@ class TbCheckAlarmStatusNodeTest { alarm.setType("General Alarm"); String msgData = JacksonUtil.toString(alarm); - TbMsg msg = getTbMsg(DEVICE_ID, msgData); + TbMsg msg = getTbMsg(msgData); when(alarmService.findAlarmByIdAsync(TENANT_ID, ALARM_ID)).thenReturn(Futures.immediateFuture(alarm)); @@ -114,7 +114,7 @@ class TbCheckAlarmStatusNodeTest { alarm.setCleared(true); String msgData = JacksonUtil.toString(alarm); - TbMsg msg = getTbMsg(DEVICE_ID, msgData); + TbMsg msg = getTbMsg(msgData); when(alarmService.findAlarmByIdAsync(TENANT_ID, ALARM_ID)).thenReturn(Futures.immediateFuture(alarm)); @@ -140,7 +140,7 @@ class TbCheckAlarmStatusNodeTest { alarm.setCleared(true); String msgData = JacksonUtil.toString(alarm); - TbMsg msg = getTbMsg(DEVICE_ID, msgData); + TbMsg msg = getTbMsg(msgData); when(alarmService.findAlarmByIdAsync(TENANT_ID, ALARM_ID)).thenReturn(Futures.immediateFuture(null)); @@ -159,8 +159,8 @@ class TbCheckAlarmStatusNodeTest { assertThat(value).isInstanceOf(TbNodeException.class).hasMessage("No such alarm found."); } - private TbMsg getTbMsg(EntityId entityId, String msgData) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), msgData); + private TbMsg getTbMsg(String msgData) { + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, EMPTY_METADATA, msgData); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java index dd25363aed..23d6711088 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java @@ -50,9 +50,9 @@ class TbCheckMessageNodeTest { private static final String EMPTY_DATA = "{}"; private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, EMPTY_METADATA, EMPTY_DATA); - private static TbCheckMessageNode node; + private TbCheckMessageNode node; - private static TbContext ctx; + private TbContext ctx; @BeforeEach void setUp() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java new file mode 100644 index 0000000000..b5cdb698a3 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java @@ -0,0 +1,297 @@ +/** + * 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.filter; + +import com.google.common.util.concurrent.Futures; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; +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.id.AssetId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.relation.RelationService; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +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.verify; +import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbCheckRelationNodeTest { + + private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); + private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); + private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); + private static final String EMPTY_DATA = "{}"; + private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, EMPTY_METADATA, EMPTY_DATA); + + private TbCheckRelationNode node; + + private TbContext ctx; + private RelationService relationService; + + @BeforeEach + void setUp() { + ctx = mock(TbContext.class); + relationService = mock(RelationService.class); + + when(ctx.getTenantId()).thenReturn(TENANT_ID); + when(ctx.getRelationService()).thenReturn(relationService); + when(ctx.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + node = new TbCheckRelationNode(); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenDefaultConfig_whenInit_then_throwException() { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))); + + // THEN + assertThat(exception.getMessage()).isEqualTo("Entity should be specified!"); + } + + @Test + void givenCustomConfigWithCheckRelationToSpecificEntity_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + + AssetId assetId = new AssetId(UUID.randomUUID()); + config.setEntityType(assetId.getEntityType().name()); + config.setEntityId(assetId.getId().toString()); + + when(relationService.checkRelationAsync(TENANT_ID, assetId, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(true)); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigWithCheckRelationToSpecificEntity_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + + AssetId assetId = new AssetId(UUID.randomUUID()); + config.setEntityType(assetId.getEntityType().name()); + config.setEntityId(assetId.getId().toString()); + + when(relationService.checkRelationAsync(TENANT_ID, assetId, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(false)); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigWithCheckRelationToSpecificEntityAndDirectionTo_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + + AssetId assetId = new AssetId(UUID.randomUUID()); + config.setEntityType(assetId.getEntityType().name()); + config.setEntityId(assetId.getId().toString()); + config.setDirection(EntitySearchDirection.TO.name()); + + when(relationService.checkRelationAsync(TENANT_ID, DEVICE_ID, assetId, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(true)); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigWithCheckRelationToSpecificEntityAndDirectionTo_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + + AssetId assetId = new AssetId(UUID.randomUUID()); + config.setEntityType(assetId.getEntityType().name()); + config.setEntityId(assetId.getId().toString()); + config.setDirection(EntitySearchDirection.TO.name()); + + when(relationService.checkRelationAsync(TENANT_ID, DEVICE_ID, assetId, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(false)); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfig_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + config.setCheckForSingleEntity(false); + var entityRelation = new EntityRelation(); + entityRelation.setTo(DEVICE_ID); + entityRelation.setFrom(new AssetId(UUID.randomUUID())); + entityRelation.setType(EntityRelation.CONTAINS_TYPE); + entityRelation.setTypeGroup(RelationTypeGroup.COMMON); + + when(relationService.findByToAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfig_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + config.setCheckForSingleEntity(false); + + when(relationService.findByToAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList())); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigDirectionTo_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + config.setCheckForSingleEntity(false); + config.setDirection(EntitySearchDirection.TO.name()); + var entityRelation = new EntityRelation(); + entityRelation.setFrom(new AssetId(UUID.randomUUID())); + entityRelation.setTo(DEVICE_ID); + entityRelation.setType(EntityRelation.CONTAINS_TYPE); + entityRelation.setTypeGroup(RelationTypeGroup.COMMON); + + when(relationService.findByFromAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigDirectionTo_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + config.setCheckForSingleEntity(false); + config.setDirection(EntitySearchDirection.TO.name()); + + when(relationService.findByFromAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList())); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java index fb77b5dc5d..ef76787f94 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java @@ -50,34 +50,30 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R class TbDeviceTypeSwitchNodeTest { - TenantId tenantId; - DeviceId deviceId; - DeviceId deviceIdDeleted; - DeviceProfile deviceProfile; - TbContext ctx; - TbDeviceTypeSwitchNode node; - EmptyNodeConfiguration config; - TbMsgCallback callback; - RuleEngineDeviceProfileCache deviceProfileCache; + private DeviceId deviceId; + private DeviceId deviceIdDeleted; + private TbContext ctx; + private TbDeviceTypeSwitchNode node; + private TbMsgCallback callback; @BeforeEach void setUp() throws TbNodeException { - tenantId = new TenantId(UUID.randomUUID()); + TenantId tenantId = new TenantId(UUID.randomUUID()); deviceId = new DeviceId(UUID.randomUUID()); deviceIdDeleted = new DeviceId(UUID.randomUUID()); - deviceProfile = new DeviceProfile(); + DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setTenantId(tenantId); deviceProfile.setName("TestDeviceProfile"); //node - config = new EmptyNodeConfiguration(); + EmptyNodeConfiguration config = new EmptyNodeConfiguration(); node = new TbDeviceTypeSwitchNode(); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); //init mock ctx = mock(TbContext.class); - deviceProfileCache = mock(RuleEngineDeviceProfileCache.class); + RuleEngineDeviceProfileCache deviceProfileCache = mock(RuleEngineDeviceProfileCache.class); callback = mock(TbMsgCallback.class); when(ctx.getTenantId()).thenReturn(tenantId); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 0b9b2ec143..b49dd4aac8 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -53,8 +53,8 @@ public class TbJsFilterNodeTest { @Mock private ScriptEngine scriptEngine; - private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); - private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); + private final RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); + private final RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); @Test public void falseEvaluationDoNotSendMsg() throws TbNodeException { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java index f4f852c6bd..763af2b1ee 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java @@ -47,8 +47,8 @@ public class TbJsSwitchNodeTest { @Mock private ScriptEngine scriptEngine; - private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); - private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); + private final RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); + private final RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); @Test public void multipleRoutesAreAllowed() throws TbNodeException { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java index 51155688e6..d43d309cda 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java @@ -43,9 +43,9 @@ class TbMsgTypeSwitchNodeTest { private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); private static final String EMPTY_DATA = "{}"; - private static TbMsgTypeSwitchNode node; + private TbMsgTypeSwitchNode node; - private static TbContext ctx; + private TbContext ctx; @BeforeEach void setUp() { @@ -84,9 +84,8 @@ class TbMsgTypeSwitchNodeTest { assertThat(msg).isNotNull(); assertThat(msg.getType()).isNotNull(); assertThat(msg).isSameAs(tbMsgList.get(i)); - // todo add additional validation that types like ALARM or PROVISION returns OTHER for backward-compatibility. assertThat(resultNodeConnections.get(i)) - .isEqualTo(TbMsgType.getRuleNodeConnection(msg.getType())); + .isEqualTo(TbMsgType.getRuleNodeConnectionOrElseOther(msg.getType())); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java index 09e67e45ef..28d8b55264 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java @@ -45,9 +45,9 @@ class TbOriginatorTypeSwitchNodeTest { private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); private static final String EMPTY_DATA = "{}"; - private static TbOriginatorTypeSwitchNode node; + private TbOriginatorTypeSwitchNode node; - private static TbContext ctx; + private TbContext ctx; @BeforeEach void setUp() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 27a13893c8..12385f6e28 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -208,7 +208,7 @@ public class TbGetCustomerAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -223,7 +223,7 @@ public class TbGetCustomerAttributeNodeTest { // GIVEN var userId = new UserId(UUID.randomUUID()); - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), userId, new TbMsgMetaData(), "{}"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), userId, new TbMsgMetaData(), "{}"); when(ctxMock.getTenantId()).thenReturn(TENANT_ID); @@ -467,7 +467,7 @@ public class TbGetCustomerAttributeNodeTest { var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index d90d9eb767..540a260338 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -456,7 +456,7 @@ public class TbGetCustomerDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); } private void mockFindCustomer() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index fc61bc4849..9c50e35f79 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -162,7 +162,7 @@ public class TbGetOriginatorFieldsNodeTest { node.fetchTo = FetchTo.DATA; var msgMetaData = new TbMsgMetaData(); var msgData = "{\"temp\":42,\"humidity\":77}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -205,7 +205,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -253,7 +253,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -311,7 +311,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), new DashboardId(UUID.randomUUID()), msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), new DashboardId(UUID.randomUUID()), msgMetaData, msgData); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index d2c452afd4..1a638eca39 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -222,7 +222,7 @@ public class TbGetRelatedAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index 0ee3512288..c163d68733 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -188,7 +188,7 @@ public class TbGetTenantAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -396,7 +396,7 @@ public class TbGetTenantAttributeNodeTest { var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java index 0a756575e2..430a772269 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java @@ -287,7 +287,7 @@ public class TbGetTenantDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); } private void mockFindTenant() { From e32dc47ea58b3f5e4ec92e3ec7e8b7294cc041c4 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 30 Jun 2023 17:42:53 +0300 Subject: [PATCH 025/168] added upgrade script for check field presence node && PROD-2217 --- .../engine/filter/TbCheckRelationNode.java | 44 ++++++++++++--- .../TbCheckRelationNodeConfiguration.java | 2 +- .../filter/TbCheckRelationNodeTest.java | 55 +++++++++++++------ ...nator_fields_node_fields_templatization.md | 2 +- 4 files changed, 74 insertions(+), 29 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index 2186378527..ecc06f6303 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -15,23 +15,26 @@ */ package org.thingsboard.rule.engine.filter; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; 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.server.common.data.StringUtils; -import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbVersionedNode; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import java.util.List; @@ -54,7 +57,9 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckRelationConfig") -public class TbCheckRelationNode implements TbNode { +public class TbCheckRelationNode implements TbVersionedNode { + + private static final String DIRECTION_PROPERTY_NAME = "direction"; private TbCheckRelationNodeConfiguration config; private EntityId singleEntityId; @@ -84,19 +89,19 @@ public class TbCheckRelationNode implements TbNode { EntityId from; EntityId to; if (EntitySearchDirection.FROM.name().equals(config.getDirection())) { - from = singleEntityId; - to = msg.getOriginator(); - } else { to = singleEntityId; from = msg.getOriginator(); + } else { + from = singleEntityId; + to = msg.getOriginator(); } return ctx.getRelationService().checkRelationAsync(ctx.getTenantId(), from, to, config.getRelationType(), RelationTypeGroup.COMMON); } private ListenableFuture processList(TbContext ctx, TbMsg msg) { ListenableFuture> relationListFuture = EntitySearchDirection.FROM.name().equals(config.getDirection()) ? - ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON) : - ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON); + ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON) : + ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON); return Futures.transformAsync(relationListFuture, this::isEmptyList, ctx.getDbCallbackExecutor()); } @@ -104,4 +109,25 @@ public class TbCheckRelationNode implements TbNode { return entityRelations.isEmpty() ? Futures.immediateFuture(false) : Futures.immediateFuture(true); } + @Override + public TbPair upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { + if (fromVersion == 0) { + var newConfigObjectNode = (ObjectNode) oldConfiguration; + if (!newConfigObjectNode.has(DIRECTION_PROPERTY_NAME)) { + throw new TbNodeException("property to update: '" + DIRECTION_PROPERTY_NAME + "' doesn't exists in configuration!"); + } + String direction = newConfigObjectNode.get(DIRECTION_PROPERTY_NAME).asText(); + if ("TO".equals(direction)) { + newConfigObjectNode.put(DIRECTION_PROPERTY_NAME, EntitySearchDirection.FROM.name()); + return new TbPair<>(true, newConfigObjectNode); + } + if ("FROM".equals(direction)) { + newConfigObjectNode.put(DIRECTION_PROPERTY_NAME, EntitySearchDirection.TO.name()); + return new TbPair<>(true, newConfigObjectNode); + } + throw new TbNodeException("property to update: '" + DIRECTION_PROPERTY_NAME + "' has invalid value!"); + } + return new TbPair<>(false, oldConfiguration); + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java index ad5574ec8c..1a8ab7068d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java @@ -34,7 +34,7 @@ public class TbCheckRelationNodeConfiguration implements NodeConfiguration newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); verify(ctx, never()).tellFailure(any(), any()); - verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); TbMsg newMsg = newMsgCaptor.getValue(); assertThat(newMsg).isNotNull(); assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); @@ -179,7 +183,7 @@ class TbCheckRelationNodeTest { config.setEntityId(assetId.getId().toString()); config.setDirection(EntitySearchDirection.TO.name()); - when(relationService.checkRelationAsync(TENANT_ID, DEVICE_ID, assetId, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(false)); + when(relationService.checkRelationAsync(TENANT_ID, assetId, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(false)); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); // WHEN @@ -200,12 +204,12 @@ class TbCheckRelationNodeTest { var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); config.setCheckForSingleEntity(false); var entityRelation = new EntityRelation(); - entityRelation.setTo(DEVICE_ID); - entityRelation.setFrom(new AssetId(UUID.randomUUID())); + entityRelation.setFrom(ORIGINATOR_ID); + entityRelation.setTo(new AssetId(UUID.randomUUID())); entityRelation.setType(EntityRelation.CONTAINS_TYPE); entityRelation.setTypeGroup(RelationTypeGroup.COMMON); - when(relationService.findByToAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + when(relationService.findByFromAndTypeAsync(TENANT_ID, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation))); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); // WHEN @@ -215,7 +219,7 @@ class TbCheckRelationNodeTest { ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); verify(ctx, never()).tellFailure(any(), any()); - verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); + verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any()); TbMsg newMsg = newMsgCaptor.getValue(); assertThat(newMsg).isNotNull(); assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); @@ -227,7 +231,7 @@ class TbCheckRelationNodeTest { var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); config.setCheckForSingleEntity(false); - when(relationService.findByToAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList())); + when(relationService.findByFromAndTypeAsync(TENANT_ID, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList())); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); // WHEN @@ -237,7 +241,7 @@ class TbCheckRelationNodeTest { ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); verify(ctx, never()).tellFailure(any(), any()); - verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); + verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any()); TbMsg newMsg = newMsgCaptor.getValue(); assertThat(newMsg).isNotNull(); assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); @@ -251,11 +255,11 @@ class TbCheckRelationNodeTest { config.setDirection(EntitySearchDirection.TO.name()); var entityRelation = new EntityRelation(); entityRelation.setFrom(new AssetId(UUID.randomUUID())); - entityRelation.setTo(DEVICE_ID); + entityRelation.setTo(ORIGINATOR_ID); entityRelation.setType(EntityRelation.CONTAINS_TYPE); entityRelation.setTypeGroup(RelationTypeGroup.COMMON); - when(relationService.findByFromAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + when(relationService.findByToAndTypeAsync(TENANT_ID, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation))); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); // WHEN @@ -265,7 +269,7 @@ class TbCheckRelationNodeTest { ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); verify(ctx, never()).tellFailure(any(), any()); - verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any()); + verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); TbMsg newMsg = newMsgCaptor.getValue(); assertThat(newMsg).isNotNull(); assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); @@ -278,7 +282,7 @@ class TbCheckRelationNodeTest { config.setCheckForSingleEntity(false); config.setDirection(EntitySearchDirection.TO.name()); - when(relationService.findByFromAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList())); + when(relationService.findByToAndTypeAsync(TENANT_ID, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList())); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); // WHEN @@ -288,10 +292,25 @@ class TbCheckRelationNodeTest { ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); verify(ctx, never()).tellFailure(any(), any()); - verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any()); + verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); TbMsg newMsg = newMsgCaptor.getValue(); assertThat(newMsg).isNotNull(); assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); } + @Test + void givenOldConfig_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + config.setEntityType(ORIGINATOR_ID.getEntityType().name()); + config.setEntityId(ORIGINATOR_ID.getId().toString()); + String oldConfig = "{\"checkForSingleEntity\":true,\"direction\":\"TO\",\"entityType\":\"" + config.getEntityType() + "\",\"entityId\":\"" + config.getEntityId() + "\",\"relationType\":\"Contains\"}"; + JsonNode configJson = JacksonUtil.toJsonNode(oldConfig); + // WHEN + TbPair upgrade = node.upgrade(0, configJson); + // THEN + assertTrue(upgrade.getFirst()); + assertEquals(config, JacksonUtil.treeToValue(upgrade.getSecond(), config.getClass())); + } + } diff --git a/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md b/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md index b21f450ae9..42f4ea6138 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md @@ -12,7 +12,7 @@ Let's assume that we have two device types in our use case: - `smart_door_lock` - `motion_detector` -Let's assume that device of type `dock_lock_sensor` and name `SDL-001` publish next type of messages to the system: +Let's assume that device of type `smart_door_lock` and name `SDL-001` publish next type of messages to the system: ```json { From 564b892786753aef145aa666f208b1aed435c015 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 3 Jul 2023 12:15:42 +0300 Subject: [PATCH 026/168] PROD-2240 & added missing version to check relation presence node --- .../engine/filter/TbCheckRelationNode.java | 1 + .../metadata/TbAbstractGetAttributesNode.java | 15 ++++++++++ .../metadata/TbAbstractNodeWithFetchTo.java | 28 ++++++++++++------- .../metadata/TbGetCustomerAttributeNode.java | 4 +-- .../metadata/TbGetAttributesNodeTest.java | 16 +++++++++++ .../metadata/TbGetDeviceAttrNodeTest.java | 18 ++++++++++++ 6 files changed, 69 insertions(+), 13 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index ecc06f6303..632c1af04f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -49,6 +49,7 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; type = ComponentType.FILTER, name = "check relation presence", configClazz = TbCheckRelationNodeConfiguration.class, + version = 1, relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Checks the presence of the relation between the originator of the message and other entities.", nodeDetails = "If 'check relation to specific entity' is selected, you should specify a related entity. " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java index ffa735fd4f..6b148f2dd4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java @@ -15,6 +15,7 @@ */ package org.thingsboard.rule.engine.metadata; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -75,6 +76,20 @@ public abstract class TbAbstractGetAttributesNode findEntityIdAsync(TbContext ctx, TbMsg msg); + protected TbPair upgradeRuleNodesWithOldPropertyToUseFetchTo( + JsonNode oldConfiguration, + String oldProperty, + String ifTrue, + String ifFalse + ) throws TbNodeException { + var newConfigObjectNode = (ObjectNode) oldConfiguration; + if (!newConfigObjectNode.has(oldProperty)) { + newConfigObjectNode.put(FETCH_TO_PROPERTY_NAME, FetchTo.METADATA.name()); + return new TbPair<>(true, newConfigObjectNode); + } + return upgradeConfigurationToUseFetchTo(oldProperty, ifTrue, ifFalse, newConfigObjectNode); + } + private void safePutAttributes(TbContext ctx, TbMsg msg, ObjectNode msgDataNode, T entityId) { Set>> failuresPairSet = ConcurrentHashMap.newKeySet(); var getKvEntryPairFutures = Futures.allAsList( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java index 16b51d02c0..2370bfde29 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java @@ -96,21 +96,29 @@ public abstract class TbAbstractNodeWithFetchTo upgradeConfigurationToUseFetchTo( + String oldProperty, String ifTrue, + String ifFalse, ObjectNode newConfig + ) throws TbNodeException { + var value = newConfig.get(oldProperty).asText(); if ("true".equals(value)) { - newConfigObjectNode.remove(oldProperty); - newConfigObjectNode.put(FETCH_TO_PROPERTY_NAME, ifTrue); - return new TbPair<>(true, newConfigObjectNode); + newConfig.remove(oldProperty); + newConfig.put(FETCH_TO_PROPERTY_NAME, ifTrue); + return new TbPair<>(true, newConfig); } else if ("false".equals(value)) { - newConfigObjectNode.remove(oldProperty); - newConfigObjectNode.put(FETCH_TO_PROPERTY_NAME, ifFalse); - return new TbPair<>(true, newConfigObjectNode); + newConfig.remove(oldProperty); + newConfig.put(FETCH_TO_PROPERTY_NAME, ifFalse); + return new TbPair<>(true, newConfig); } else { - throw new TbNodeException("property to update: '" + oldProperty + "' has unexpected value: " + value + ". Allowed values: true or false!"); + throw new TbNodeException("property to update: '" + oldProperty + "' has unexpected value: " + + value + ". Allowed values: true or false!"); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index 3cf69b5e26..f5b50f259c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -64,9 +64,7 @@ public class TbGetCustomerAttributeNode extends TbAbstractGetEntityDataNode upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { - return fromVersion == 0 ? - upgradeToUseFetchToAndDataToFetch(oldConfiguration) : - new TbPair<>(false, oldConfiguration); + return fromVersion == 0 ? upgradeToUseFetchToAndDataToFetch(oldConfiguration) : new TbPair<>(false, oldConfiguration); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java index 5146f2f1e9..cb36982b20 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java @@ -267,6 +267,22 @@ public class TbGetAttributesNodeTest { Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); } + @Test + public void givenOldConfigWithNoFetchToDataProperty_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception { + var defaultConfig = new TbGetAttributesNodeConfiguration().defaultConfiguration(); + var node = new TbGetAttributesNode(); + String oldConfig = "{\"clientAttributeNames\":[]," + + "\"sharedAttributeNames\":[]," + + "\"serverAttributeNames\":[]," + + "\"latestTsKeyNames\":[]," + + "\"tellFailureIfAbsent\":true," + + "\"getLatestValueWithTs\":false}"; + JsonNode configJson = JacksonUtil.toJsonNode(oldConfig); + TbPair upgrade = node.upgrade(0, configJson); + Assertions.assertTrue(upgrade.getFirst()); + Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); + } + private TbMsg checkMsg(boolean checkSuccess) { var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); if (checkSuccess) { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeTest.java index 82e24226f5..bdb431227b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeTest.java @@ -42,4 +42,22 @@ public class TbGetDeviceAttrNodeTest { Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); } + @Test + public void givenOldConfigWithNoFetchToDataProperty_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception { + var defaultConfig = new TbGetDeviceAttrNodeConfiguration().defaultConfiguration(); + var node = new TbGetDeviceAttrNode(); + String oldConfig = "{\"clientAttributeNames\":[]," + + "\"sharedAttributeNames\":[]," + + "\"serverAttributeNames\":[]," + + "\"latestTsKeyNames\":[]," + + "\"tellFailureIfAbsent\":true," + + "\"getLatestValueWithTs\":false," + + "\"deviceRelationsQuery\":{\"direction\":\"FROM\",\"maxLevel\":1,\"relationType\":\"Contains\",\"deviceTypes\":[\"default\"]," + + "\"fetchLastLevelOnly\":false}}"; + JsonNode configJson = JacksonUtil.toJsonNode(oldConfig); + TbPair upgrade = node.upgrade(0, configJson); + Assertions.assertTrue(upgrade.getFirst()); + Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); + } + } \ No newline at end of file From 6c74aa3dff0552dc2dca97337743975fb6798a40 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 3 Jul 2023 13:26:20 +0300 Subject: [PATCH 027/168] updated EntityFieldsData getFieldValue method to filter out empty strings if ignoreNullStrings is set to true --- .../server/common/data/EntityFieldsData.java | 26 ++++++------ .../server/common/data/StringUtils.java | 4 +- .../TbGetOriginatorFieldsNodeTest.java | 42 +++++++++++++++++++ 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityFieldsData.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityFieldsData.java index c88a6b970f..db069d8d61 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityFieldsData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityFieldsData.java @@ -68,20 +68,22 @@ public class EntityFieldsData { break; } } - if (current != null) { - if(current.isNull() && ignoreNullStrings){ + if (current == null) { + return null; + } + if (current.isNull() && ignoreNullStrings) { + return null; + } + if (current.isValueNode()) { + String textValue = current.asText(); + if (StringUtils.isEmpty(textValue) && ignoreNullStrings) { return null; } - if (current.isValueNode()) { - return current.asText(); - } else { - try { - return mapper.writeValueAsString(current); - } catch (JsonProcessingException e) { - return null; - } - } - } else { + return textValue; + } + try { + return mapper.writeValueAsString(current); + } catch (JsonProcessingException e) { return null; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java index 6b70dfc09c..1e818ac8ef 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java @@ -38,7 +38,7 @@ public class StringUtils { } public static boolean isBlank(String source) { - return source == null || source.isEmpty() || source.trim().isEmpty(); + return isEmpty(source) || source.trim().isEmpty(); } public static boolean isNotEmpty(String source) { @@ -46,7 +46,7 @@ public class StringUtils { } public static boolean isNotBlank(String source) { - return source != null && !source.isEmpty() && !source.trim().isEmpty(); + return !isBlank(source); } public static String notBlankOrDefault(String src, String def) { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index 9c50e35f79..addd05bd0e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -184,6 +184,48 @@ public class TbGetOriginatorFieldsNodeTest { assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData); } + @Test + public void givenDeviceWithEmptyLabel_whenOnMsg_thenShouldTellSuccessAndFetchToData() throws TbNodeException, ExecutionException, InterruptedException { + // GIVEN + var device = new Device(); + device.setId(DUMMY_DEVICE_ORIGINATOR); + device.setName("Test device"); + device.setType("Test device type"); + device.setLabel(""); + + config.setDataMapping(Map.of( + "name", "originatorName", + "type", "originatorType", + "label", "originatorLabel")); + config.setIgnoreNullStrings(true); + config.setFetchTo(FetchTo.DATA); + + node.config = config; + node.fetchTo = FetchTo.DATA; + var msgMetaData = new TbMsgMetaData(); + var msgData = "{\"temp\":42,\"humidity\":77}"; + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); + when(deviceServiceMock.findDeviceById(eq(DUMMY_TENANT_ID), eq(device.getId()))).thenReturn(device); + + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); + + var expectedMsgData = "{\"temp\":42,\"humidity\":77,\"originatorName\":\"Test device\",\"originatorType\":\"Test device type\"}"; + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData); + } + @Test public void givenValidMsgAndFetchToMetaData_whenOnMsg_thenShouldTellSuccessAndFetchToMetaData() throws TbNodeException, ExecutionException, InterruptedException { // GIVEN From 552be228a35ad49ca0d8939a25a12324457cd1e7 Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 3 Jul 2023 17:13:11 +0300 Subject: [PATCH 028/168] Refactoring --- .../script/node-script-test.service.ts | 6 ++- .../components/details-panel.component.ts | 5 -- .../entity/entities-table.component.ts | 4 ++ .../components/event/event-table-config.ts | 45 +++++++++-------- .../components/event/event-table.component.ts | 36 ++++++++------ .../entity/entity-table-component.models.ts | 1 + .../rulechain/rule-node-config.component.ts | 25 ++++++---- .../rule-node-details.component.html | 2 +- .../rulechain/rule-node-details.component.ts | 7 ++- .../rulechain/rulechain-page.component.html | 9 ++-- .../rulechain/rulechain-page.component.ts | 27 +++++++---- .../src/app/shared/models/rule-node.models.ts | 48 ++++++++----------- .../assets/locale/locale.constant-en_US.json | 10 +--- 13 files changed, 118 insertions(+), 107 deletions(-) diff --git a/ui-ngx/src/app/core/services/script/node-script-test.service.ts b/ui-ngx/src/app/core/services/script/node-script-test.service.ts index c3e0da73ad..fa30934f06 100644 --- a/ui-ngx/src/app/core/services/script/node-script-test.service.ts +++ b/ui-ngx/src/app/core/services/script/node-script-test.service.ts @@ -61,7 +61,8 @@ export class NodeScriptTestService { try { msg = JSON.parse(eventBody.data); } catch (e) {} - } else { + } + if (!msg) { msg = { temperature: 22.4, humidity: 78 @@ -71,7 +72,8 @@ export class NodeScriptTestService { try { metadata = JSON.parse(eventBody.metadata); } catch (e) {} - } else { + } + if (!metadata) { metadata = { deviceName: 'Test Device', deviceType: 'default', diff --git a/ui-ngx/src/app/modules/home/components/details-panel.component.ts b/ui-ngx/src/app/modules/home/components/details-panel.component.ts index ac8d6e3bc3..66facf08d4 100644 --- a/ui-ngx/src/app/modules/home/components/details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/details-panel.component.ts @@ -56,9 +56,6 @@ export class DetailsPanelComponent extends PageComponent implements OnDestroy { this.theFormValue = value; if (this.theFormValue !== null) { this.formSubscription = this.theFormValue.valueChanges.subscribe(() => { - if (this.isReadOnly) { - this.switchToFirstTab.emit(); - } this.cd.detectChanges() }); } @@ -77,8 +74,6 @@ export class DetailsPanelComponent extends PageComponent implements OnDestroy { applyDetails = new EventEmitter(); @Output() closeSearch = new EventEmitter(); - @Output() - switchToFirstTab = new EventEmitter() isEditValue = false; showSearchPane = false; diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts index b868fd1e47..46a7c966c8 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts @@ -637,6 +637,10 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa } } + cellActionDescriptorsUpdated() { + this.cellActionDescriptors = [...this.entitiesTableConfig.cellActionDescriptors]; + } + headerCellStyle(column: EntityColumn>) { const index = this.entitiesTableConfig.columns.indexOf(column); let res = this.headerCellStyleCache[index]; diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index 51bd99fde0..7382633e33 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -30,7 +30,7 @@ import { EntityId } from '@shared/models/id/entity-id'; import { EventService } from '@app/core/http/event.service'; import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component'; import { EntityTypeResource } from '@shared/models/entity-type.models'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; import { Direction } from '@shared/models/page/sort-order'; import { DialogService } from '@core/services/dialog.service'; @@ -41,7 +41,7 @@ import { } from '@home/components/event/event-content-dialog.component'; import { isEqual, sortObjectKeys } from '@core/utils'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; -import { ChangeDetectorRef, Injector, StaticProvider, ViewContainerRef } from '@angular/core'; +import { ChangeDetectorRef, EventEmitter, Injector, StaticProvider, ViewContainerRef } from '@angular/core'; import { ComponentPortal } from '@angular/cdk/portal'; import { EVENT_FILTER_PANEL_DATA, @@ -50,7 +50,6 @@ import { FilterEntityColumn } from '@home/components/event/event-filter-panel.component'; import { NodeScriptTestService } from '@core/services/script/node-script-test.service'; -import { ruleNodeClazzFunctionNameTranslations } from '@shared/models/rule-node.models'; export class EventTableConfig extends EntityTableConfig { @@ -63,6 +62,7 @@ export class EventTableConfig extends EntityTableConfig { set eventType(eventType: EventType | DebugEventType) { if (this.eventTypeValue !== eventType) { this.eventTypeValue = eventType; + this.updateCellAction(); this.updateColumns(true); this.updateFilterColumns(); } @@ -74,8 +74,6 @@ export class EventTableConfig extends EntityTableConfig { eventTypes: Array; - debugEventSelectedSubject = new BehaviorSubject(null); - constructor(private eventService: EventService, private dialogService: DialogService, private translate: TranslateService, @@ -90,9 +88,8 @@ export class EventTableConfig extends EntityTableConfig { private viewContainerRef: ViewContainerRef, private cd: ChangeDetectorRef, private nodeScriptTestService: NodeScriptTestService, - private isRuleNodeDebugModeEnabled: boolean, - private editingRuleNodeHasScript: boolean, - private rulenodeClazz: string) { + public testButtonLabel?: string, + private debugEventSelected?: EventEmitter) { super(); this.loadDataOnInit = false; this.tableTitle = ''; @@ -128,6 +125,7 @@ export class EventTableConfig extends EntityTableConfig { this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; this.updateColumns(); + this.updateCellAction(); this.updateFilterColumns(); this.headerActionDescriptors.push({ @@ -325,18 +323,6 @@ export class EventTableConfig extends EntityTableConfig { onAction: ($event, entity) => this.showContent($event, entity.body.error, 'event.error') }, - '48px'), - new EntityActionTableColumn('test', '', - { - name: this.translate.instant('rulenode.test-function', - {function: this.translate.instant(ruleNodeClazzFunctionNameTranslations[this.rulenodeClazz])}), - icon: 'bug_report', - isEnabled: (entity) => this.isRuleNodeDebugModeEnabled && entity.body.type === 'IN' && - this.editingRuleNodeHasScript, - onAction: ($event, entity) => { - this.debugEventSelectedSubject.next(entity.body); - } - }, '48px') ); break; @@ -369,6 +355,25 @@ export class EventTableConfig extends EntityTableConfig { } } + updateCellAction() { + this.cellActionDescriptors = []; + switch (this.eventType) { + case DebugEventType.DEBUG_RULE_NODE: + if (this.testButtonLabel) { + this.cellActionDescriptors.push({ + name: this.translate.instant('rulenode.test-with-this-message', {test: this.testButtonLabel}), + icon: 'bug_report', + isEnabled: (entity) => entity.body.type === 'IN', + onAction: ($event, entity) => { + this.debugEventSelected.next(entity.body); + } + }); + } + break; + } + this.getTable()?.cellActionDescriptorsUpdated(); + } + showContent($event: MouseEvent, content: string, title: string, contentType: ContentType = null, sortKeys = false): void { if ($event) { $event.stopPropagation(); diff --git a/ui-ngx/src/app/modules/home/components/event/event-table.component.ts b/ui-ngx/src/app/modules/home/components/event/event-table.component.ts index 4781b4811d..80394de979 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table.component.ts @@ -36,6 +36,7 @@ import { DebugEventType, DebugRuleNodeEventBody, EventType } from '@shared/model import { Overlay } from '@angular/cdk/overlay'; import { Subscription } from 'rxjs'; import { NodeScriptTestService } from '@core/services/script/node-script-test.service'; +import { isNotEmptyStr } from '@core/utils'; @Component({ selector: 'tb-event-table', @@ -60,6 +61,10 @@ export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy { dirtyValue = false; entityIdValue: EntityId; + get active(): boolean { + return this.activeValue; + } + @Input() set active(active: boolean) { if (this.activeValue !== active) { @@ -84,14 +89,24 @@ export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy { } } - @Input() - isRuleNodeDebugModeEnabled: boolean; + private ruleNodeTestButtonLabelValue: string; - @Input() - editingRuleNodeHasScript: boolean; + get ruleNodeTestButtonLabel(): string { + return this.ruleNodeTestButtonLabelValue; + } @Input() - rulenodeClazz: string; + set ruleNodeTestButtonLabel(value: string) { + if (isNotEmptyStr(value)) { + this.ruleNodeTestButtonLabelValue = value; + } else { + this.ruleNodeTestButtonLabelValue = ''; + } + if (this.eventTableConfig) { + this.eventTableConfig.testButtonLabel = this.ruleNodeTestButtonLabel; + this.eventTableConfig.updateCellAction(); + } + } @Output() debugEventSelected = new EventEmitter(null); @@ -130,16 +145,9 @@ export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy { this.viewContainerRef, this.cd, this.nodeScriptTestService, - this.isRuleNodeDebugModeEnabled, - this.editingRuleNodeHasScript, - this.rulenodeClazz + this.ruleNodeTestButtonLabel, + this.debugEventSelected ); - - this.eventTableConfig.debugEventSelectedSubject.subscribe((debugEventBody: DebugRuleNodeEventBody) => { - if (debugEventBody) { - this.debugEventSelected.emit(debugEventBody); - } - }) } ngAfterViewInit() { diff --git a/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts b/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts index 033747f6d3..a6e5ada7bf 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts @@ -80,6 +80,7 @@ export interface IEntitiesTableComponent { exitFilterMode(): void; resetSortAndFilter(update?: boolean, preserveTimewindow?: boolean): void; columnsUpdated(resetData?: boolean): void; + cellActionDescriptorsUpdated(): void; headerCellStyle(column: EntityColumn>): any; clearCellCache(col: number, row: number): void; cellContent(entity: BaseData, column: EntityColumn>, row: number): any; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts index 788a495607..6c19507301 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts @@ -18,14 +18,22 @@ import { AfterViewInit, Component, ComponentRef, + EventEmitter, forwardRef, Input, OnDestroy, OnInit, + Output, ViewChild, ViewContainerRef } from '@angular/core'; -import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + Validators +} from '@angular/forms'; import { IRuleNodeConfigurationComponent, RuleNodeConfiguration, @@ -38,7 +46,6 @@ import { TranslateService } from '@ngx-translate/core'; import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; import { deepClone } from '@core/utils'; import { RuleChainType } from '@shared/models/rule-chain.models'; -import { DebugRuleNodeEventBody } from '@shared/models/event.models'; @Component({ selector: 'tb-rule-node-config', @@ -77,6 +84,9 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On @Input() ruleChainType: RuleChainType; + @Output() + initRuleNode = new EventEmitter(); + nodeDefinitionValue: RuleNodeDefinition; @Input() @@ -86,6 +96,7 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On if (this.nodeDefinitionValue) { this.validateDefinedDirective(); } + setTimeout(() => this.initRuleNode.emit()); } } @@ -93,21 +104,15 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On return this.nodeDefinitionValue; } - @Input() - set debugEventBody(debugEventBody: DebugRuleNodeEventBody) { - if (debugEventBody) { - this.definedConfigComponent?.testScript(debugEventBody); - } - } - definedDirectiveError: string; ruleNodeConfigFormGroup: UntypedFormGroup; changeSubscription: Subscription; + definedConfigComponent: IRuleNodeConfigurationComponent; + private definedConfigComponentRef: ComponentRef; - private definedConfigComponent: IRuleNodeConfigurationComponent; private configuration: RuleNodeConfiguration; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html index c215b0d21d..2f925ded60 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html @@ -51,7 +51,7 @@ [ruleChainId]="ruleChainId" [ruleChainType]="ruleChainType" [nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition" - [debugEventBody]="debugEventBody"> + (initRuleNode)="initRuleNode.emit($event)">
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts index 455e70270c..7b0f426c35 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -27,7 +27,6 @@ import { RuleNodeConfigComponent } from './rule-node-config.component'; import { Router } from '@angular/router'; import { RuleChainType } from '@app/shared/models/rule-chain.models'; import { ComponentClusteringMode } from '@shared/models/component-descriptor.models'; -import { DebugRuleNodeEventBody } from '@shared/models/event.models'; @Component({ selector: 'tb-rule-node', @@ -56,8 +55,8 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O @Input() isAdd = false; - @Input() - debugEventBody: DebugRuleNodeEventBody; + @Output() + initRuleNode = new EventEmitter(); ruleNodeType = RuleNodeType; entityType = EntityType; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html index 15695d9d87..5e20a6a8e2 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html @@ -100,8 +100,7 @@ (closeDetails)="onEditRuleNodeClosed()" (toggleDetailsEditMode)="onRevertRuleNodeEdit()" (applyDetails)="saveRuleNode()" - [theForm]="tbRuleNode.ruleNodeFormGroup" - (switchToFirstTab)="onSwitchToFirstTab()"> + [theForm]="tbRuleNode.ruleNodeFormGroup">
@@ -113,7 +112,7 @@ [ruleChainType]="ruleChainType" [isEdit]="true" [isReadOnly]="false" - [debugEventBody]="debugEventBody"> + (initRuleNode)="onRuleNodeInit()"> @@ -122,9 +121,7 @@ [active]="eventsTab.isActive" [tenantId]="ruleChain.tenantId.id" [entityId]="editingRuleNode.ruleNodeId" - [isRuleNodeDebugModeEnabled]="editingRuleNode?.debugMode" - [editingRuleNodeHasScript]="editingRuleNodeHasScript" - [rulenodeClazz]="editingRuleNode.component.clazz" + [ruleNodeTestButtonLabel]="ruleNodeTestButtonLabel" (debugEventSelected)="onDebugEventSelected($event)"> diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts index ff9d9060c8..d5ea093721 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts @@ -153,8 +153,7 @@ export class RuleChainPageComponent extends PageComponent editingRuleNodeAllowCustomLabels = false; editingRuleNodeLinkLabels: {[label: string]: LinkLabel}; editingRuleNodeSourceRuleChainId: string; - debugEventBody: DebugRuleNodeEventBody; - editingRuleNodeHasScript: boolean = false; + ruleNodeTestButtonLabel: string; @ViewChild('tbRuleNode') ruleNodeComponent: RuleNodeDetailsComponent; @ViewChild('tbRuleNodeLink') ruleNodeLinkComponent: RuleNodeLinkComponent; @@ -1112,7 +1111,6 @@ export class RuleChainPageComponent extends PageComponent this.isEditingRuleNode = true; this.editingRuleNodeIndex = this.ruleChainModel.nodes.indexOf(node); this.editingRuleNode = deepClone(node, ['component']); - this.editingRuleNodeHasScript = this.editingRuleNode.configuration.hasOwnProperty('scriptLang'); setTimeout(() => { this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); }, 0); @@ -1261,7 +1259,6 @@ export class RuleChainPageComponent extends PageComponent onEditRuleNodeClosed() { this.editingRuleNode = null; this.isEditingRuleNode = false; - this.debugEventBody = null; } onEditRuleNodeLinkClosed() { @@ -1282,13 +1279,25 @@ export class RuleChainPageComponent extends PageComponent } onDebugEventSelected(debugEventBody: DebugRuleNodeEventBody) { - if (debugEventBody) { - this.debugEventBody = debugEventBody; - } + if (this.ruleNodeComponent.ruleNodeConfigComponent.useDefinedDirective() && + this.ruleNodeComponent.ruleNodeConfigComponent.definedConfigComponent.getSupportTestFunction() && + this.ruleNodeComponent.ruleNodeConfigComponent.definedConfigComponent.testScript$) { + this.ruleNodeComponent.ruleNodeConfigComponent.definedConfigComponent.testScript$(debugEventBody) + .subscribe((value) => { + if (value) { + this.selectedRuleNodeTabIndex = 0; + } + }) + } } - onSwitchToFirstTab() { - this.selectedRuleNodeTabIndex = 0; + onRuleNodeInit() { + if (this.ruleNodeComponent.ruleNodeConfigComponent.useDefinedDirective() && + this.ruleNodeComponent.ruleNodeConfigComponent.definedConfigComponent.getSupportTestFunction()) { + this.ruleNodeTestButtonLabel = this.ruleNodeComponent.ruleNodeConfigComponent.definedConfigComponent.getTestButtonLabel(); + } else { + this.ruleNodeTestButtonLabel = ''; + } } saveRuleNode() { diff --git a/ui-ngx/src/app/shared/models/rule-node.models.ts b/ui-ngx/src/app/shared/models/rule-node.models.ts index 84ea8ddd3e..dd427aaf09 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -27,6 +27,7 @@ import { AppState } from '@core/core.state'; import { AbstractControl, UntypedFormGroup } from '@angular/forms'; import { RuleChainType } from '@shared/models/rule-chain.models'; import { DebugRuleNodeEventBody } from '@shared/models/event.models'; +import { TranslateService } from '@ngx-translate/core'; export interface RuleNodeConfiguration { [key: string]: any; @@ -76,7 +77,9 @@ export interface IRuleNodeConfigurationComponent { configuration: RuleNodeConfiguration; configurationChanged: Observable; validate(); - testScript? (debugEventBody: DebugRuleNodeEventBody); + getSupportTestFunction(): boolean; + getTestButtonLabel? (): string; + testScript$? (debugEventBody?: DebugRuleNodeEventBody): Observable; [key: string]: any; } @@ -112,7 +115,8 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple configurationChangedEmiter = new EventEmitter(); configurationChanged = this.configurationChangedEmiter.asObservable(); - protected constructor(@Inject(Store) protected store: Store) { + protected constructor(@Inject(Store) protected store: Store, + @Inject(TranslateService) protected translate: TranslateService) { super(store); } @@ -130,6 +134,14 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple this.onValidate(); } + getSupportTestFunction(): boolean { + return false; + } + + getTestButtonLabel(): string { + return this.translate.instant('rulenode.test-script-function'); + } + protected setupConfiguration(configuration: RuleNodeConfiguration) { this.onConfigurationSet(this.prepareInputConfig(configuration)); this.updateValidators(false); @@ -429,22 +441,13 @@ export const messageTypeNames = new Map( export const ruleChainNodeClazz = 'org.thingsboard.rule.engine.flow.TbRuleChainInputNode'; export const outputNodeClazz = 'org.thingsboard.rule.engine.flow.TbRuleChainOutputNode'; -export enum RuleNodeClazz { - TbJsFilterNode = 'org.thingsboard.rule.engine.filter.TbJsFilterNode', - TbLogNode = 'org.thingsboard.rule.engine.action.TbLogNode', - TbJsSwitchNode = 'org.thingsboard.rule.engine.filter.TbJsSwitchNode', - TbClearAlarmNode = 'org.thingsboard.rule.engine.action.TbClearAlarmNode', - TbCreateAlarmNode = 'org.thingsboard.rule.engine.action.TbCreateAlarmNode', - TbTransformMsgNode = 'org.thingsboard.rule.engine.transform.TbTransformMsgNode', - TbMsgGeneratorNode = 'org.thingsboard.rule.engine.debug.TbMsgGeneratorNode' -} const ruleNodeClazzHelpLinkMap = { 'org.thingsboard.rule.engine.filter.TbCheckRelationNode': 'ruleNodeCheckRelation', 'org.thingsboard.rule.engine.filter.TbCheckMessageNode': 'ruleNodeCheckExistenceFields', 'org.thingsboard.rule.engine.geo.TbGpsGeofencingFilterNode': 'ruleNodeGpsGeofencingFilter', - [RuleNodeClazz.TbJsFilterNode]: 'ruleNodeJsFilter', - [RuleNodeClazz.TbJsSwitchNode]: 'ruleNodeJsSwitch', + 'org.thingsboard.rule.engine.filter.TbJsFilterNode': 'ruleNodeJsFilter', + 'org.thingsboard.rule.engine.filter.TbJsSwitchNode': 'ruleNodeJsSwitch', 'org.thingsboard.rule.engine.filter.TbAssetTypeSwitchNode': 'ruleNodeAssetProfileSwitch', 'org.thingsboard.rule.engine.filter.TbDeviceTypeSwitchNode': 'ruleNodeDeviceProfileSwitch', 'org.thingsboard.rule.engine.filter.TbCheckAlarmStatusNode': 'ruleNodeCheckAlarmStatus', @@ -463,18 +466,18 @@ const ruleNodeClazzHelpLinkMap = { 'org.thingsboard.rule.engine.metadata.TbGetTenantDetailsNode': 'ruleNodeTenantDetails', 'org.thingsboard.rule.engine.metadata.CalculateDeltaNode': 'ruleNodeCalculateDelta', 'org.thingsboard.rule.engine.transform.TbChangeOriginatorNode': 'ruleNodeChangeOriginator', - [RuleNodeClazz.TbTransformMsgNode]: 'ruleNodeTransformMsg', + 'org.thingsboard.rule.engine.transform.TbTransformMsgNode': 'ruleNodeTransformMsg', 'org.thingsboard.rule.engine.mail.TbMsgToEmailNode': 'ruleNodeMsgToEmail', 'org.thingsboard.rule.engine.action.TbAssignToCustomerNode': 'ruleNodeAssignToCustomer', 'org.thingsboard.rule.engine.action.TbUnassignFromCustomerNode': 'ruleNodeUnassignFromCustomer', - [RuleNodeClazz.TbClearAlarmNode]: 'ruleNodeClearAlarm', - [RuleNodeClazz.TbCreateAlarmNode]: 'ruleNodeCreateAlarm', + 'org.thingsboard.rule.engine.action.TbClearAlarmNode': 'ruleNodeClearAlarm', + 'org.thingsboard.rule.engine.action.TbCreateAlarmNode': 'ruleNodeCreateAlarm', 'org.thingsboard.rule.engine.action.TbCreateRelationNode': 'ruleNodeCreateRelation', 'org.thingsboard.rule.engine.action.TbDeleteRelationNode': 'ruleNodeDeleteRelation', 'org.thingsboard.rule.engine.delay.TbMsgDelayNode': 'ruleNodeMsgDelay', - [RuleNodeClazz.TbMsgGeneratorNode]: 'ruleNodeMsgGenerator', + 'org.thingsboard.rule.engine.debug.TbMsgGeneratorNode': 'ruleNodeMsgGenerator', 'org.thingsboard.rule.engine.geo.TbGpsGeofencingActionNode': 'ruleNodeGpsGeofencingEvents', - [RuleNodeClazz.TbLogNode]: 'ruleNodeLog', + 'org.thingsboard.rule.engine.action.TbLogNode': 'ruleNodeLog', 'org.thingsboard.rule.engine.rpc.TbSendRPCReplyNode': 'ruleNodeRpcCallReply', 'org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode': 'ruleNodeRpcCallRequest', 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode': 'ruleNodeSaveAttributes', @@ -511,12 +514,3 @@ export function getRuleNodeHelpLink(component: RuleNodeComponentDescriptor): str return 'ruleEngine'; } -export const ruleNodeClazzFunctionNameTranslations = { - [RuleNodeClazz.TbJsFilterNode]: 'rulenode.function-name.filter', - [RuleNodeClazz.TbLogNode]: 'rulenode.function-name.to-string', - [RuleNodeClazz.TbJsSwitchNode]: 'rulenode.function-name.switch', - [RuleNodeClazz.TbClearAlarmNode]: 'rulenode.function-name.details', - [RuleNodeClazz.TbCreateAlarmNode]: 'rulenode.function-name.details', - [RuleNodeClazz.TbTransformMsgNode]: 'rulenode.function-name.transform', - [RuleNodeClazz.TbMsgGeneratorNode]: 'rulenode.function-name.generate' -} 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 d3b374f1ec..ef94480aa8 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3467,15 +3467,7 @@ "test": "Test", "help": "Help", "reset-debug-mode": "Reset debug mode in all nodes", - "test-function": "Test '{{function}}' function with this message", - "function-name": { - "filter": "Filter", - "to-string": "ToString", - "details": "Details", - "generate": "Generate", - "switch": "Switch", - "transform": "Transform" - } + "test-with-this-message": "{{test}} with this message" }, "timezone": { "timezone": "Timezone", From fee8aa359a126cedddf0814f7fbf1cfbaecb483e Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 4 Jul 2023 18:18:23 +0300 Subject: [PATCH 029/168] refactoring --- .../server/controller/DeviceController.java | 3 +- .../src/main/resources/thingsboard.yml | 16 ++ .../server/dao/device/DeviceService.java | 3 +- .../DeviceConnectivityConfiguration.java | 9 + .../server/dao/device/DeviceServiceImpl.java | 176 ++++++++++++------ 5 files changed, 151 insertions(+), 56 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index e473163642..100fa8234c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -80,6 +80,7 @@ import javax.servlet.http.HttpServletRequest; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -168,7 +169,7 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device/{deviceId}/commands", method = RequestMethod.GET) @ResponseBody - public List getDevicePublishTelemetryCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + public Map getDevicePublishTelemetryCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index e7fbbd2a3d..1c044daa74 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -775,6 +775,10 @@ transport: worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}" max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}" so_keep_alive: "${NETTY_SO_KEEPALIVE:false}" + # Mqtt device connectivity host to publish telemetry + device_connectivity_host: "${MQTT_DEVICE_CONNECTIVITY_HOST:localhost}" + # Mqtt device connectivity port to publish telemetry + device_connectivity_port: "${MQTT_DEVICE_CONNECTIVITY_PORT:1883}" # MQTT SSL configuration ssl: # Enable/disable SSL support @@ -785,6 +789,10 @@ transport: bind_port: "${MQTT_SSL_BIND_PORT:8883}" # SSL protocol: See https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#sslcontext-algorithms protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}" + # Mqtt ssl device connectivity host to publish telemetry + device_connectivity_host: "${MQTT_DEVICE_CONNECTIVITY_HOST:localhost}" + # Mqtt ssl device connectivity port to publish telemetry + device_connectivity_port: "${MQTT_DEVICE_CONNECTIVITY_PORT:8883}" # Server SSL credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -821,6 +829,10 @@ transport: piggyback_timeout: "${COAP_PIGGYBACK_TIMEOUT:500}" psm_activity_timer: "${COAP_PSM_ACTIVITY_TIMER:10000}" paging_transmission_window: "${COAP_PAGING_TRANSMISSION_WINDOW:10000}" + # Coap device connectivity host to publish telemetry + device_connectivity_host: "${COAP_DEVICE_CONNECTIVITY_HOST:localhost}" + # Coap device connectivity port to publish telemetry + device_connectivity_port: "${COAP_DEVICE_CONNECTIVITY_PORT:5683}" dtls: # Enable/disable DTLS 1.2 support enabled: "${COAP_DTLS_ENABLED:false}" @@ -830,6 +842,10 @@ transport: bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" + # Coap DTLS device connectivity host to publish telemetry + device_connectivity_host: "${COAP_DEVICE_CONNECTIVITY_HOST:localhost}" + # Coap DTLS device connectivity port to publish telemetry + device_connectivity_port: "${COAP_DEVICE_CONNECTIVITY_PORT:5684}" # Server DTLS credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index 79f4781936..72c6a8852c 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -38,13 +38,14 @@ import org.thingsboard.server.dao.entity.EntityDaoService; import java.net.URISyntaxException; import java.util.List; +import java.util.Map; import java.util.UUID; public interface DeviceService extends EntityDaoService { DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId); - List findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException; + Map findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException; Device findDeviceById(TenantId tenantId, DeviceId deviceId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java new file mode 100644 index 0000000000..f156729cbc --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java @@ -0,0 +1,9 @@ +package org.thingsboard.server.dao.device; + +import lombok.Data; + +@Data +public class DeviceConnectivityConfiguration { + private String deviceConnectivityHost; + private Integer deviceConnectivityPort; +} 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 82d380056b..cca89742e1 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 @@ -20,6 +20,9 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; @@ -51,6 +54,7 @@ import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfigu import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.EfentoCoapDeviceTypeConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; @@ -79,11 +83,11 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; -import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -124,6 +128,46 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException { + public Map findDevicePublishTelemetryCommands(String baseUrl, Device device) { DeviceId deviceId = device.getId(); log.trace("Executing findDevicePublishTelemetryCommands [{}]", deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); - String hostname = new URI(baseUrl).getHost(); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); - DeviceCredentialsType credentialsType = deviceCredentials.getCredentialsType(); + DeviceCredentials creds = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); + DeviceCredentialsType credentialsType = creds.getCredentialsType(); DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); + DeviceTransportType transportType = deviceProfile.getTransportType(); + + Map commands = new HashMap<>(); - ArrayList commands = new ArrayList<>(); - switch (deviceProfile.getTransportType()) { + switch (transportType) { case DEFAULT: - switch (credentialsType) { + switch (credentialsType) { case ACCESS_TOKEN: - commands.add(getMqttAccessTokenCommand(hostname, deviceCredentials) + " -m " + PAYLOAD); - commands.add(getHttpAccessTokenCommand(baseUrl, deviceCredentials)); - commands.add("echo -n " + PAYLOAD + " | " + getCoapAccessTokenCommand(hostname, deviceCredentials) + " -f-"); - break; + commands.put("http", getHttpPublishCommand(baseUrl, creds)); + commands.put("mqtt", getMqttPublishCommand(mqttProperties.getDeviceConnectivityHost(), mqttProperties.getDeviceConnectivityPort(), creds)); + commands.put("mqtts", getMqttPublishCommand(mqttsProperties.getDeviceConnectivityHost(), mqttsProperties.getDeviceConnectivityPort(), creds)); + commands.put("coap", getCoapPublishCommand(coapProperties.getDeviceConnectivityHost(), coapProperties.getDeviceConnectivityPort(), creds)); + commands.put("coaps", getCoapPublishCommand(coapsProperties.getDeviceConnectivityHost(), coapsProperties.getDeviceConnectivityPort(), creds)); break; case MQTT_BASIC: - commands.add(getMqttBasicPublishCommand(hostname, deviceCredentials) + " -m " + PAYLOAD); + commands.put("mqtt", getMqttPublishCommand(mqttProperties.getDeviceConnectivityHost(), mqttProperties.getDeviceConnectivityPort(), creds)); + commands.put("mqtts", getMqttPublishCommand(mqttsProperties.getDeviceConnectivityHost(), mqttsProperties.getDeviceConnectivityPort(), creds)); break; case X509_CERTIFICATE: - commands.add(getMqttX509Command(hostname) + " -m " + PAYLOAD); + commands.put("mqtt", getMqttPublishCommand(mqttProperties.getDeviceConnectivityHost(), mqttProperties.getDeviceConnectivityPort(), creds)); + commands.put("mqtts", getMqttPublishCommand(mqttsProperties.getDeviceConnectivityHost(), mqttsProperties.getDeviceConnectivityPort(), creds)); + commands.put("coap", getCoapPublishCommand(coapProperties.getDeviceConnectivityHost(), coapProperties.getDeviceConnectivityPort(), creds)); + commands.put("coaps", getCoapPublishCommand(coapsProperties.getDeviceConnectivityHost(), coapsProperties.getDeviceConnectivityPort(), creds)); break; } break; case MQTT: MqttDeviceProfileTransportConfiguration transportConfiguration = (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + String topicName = transportConfiguration.getDeviceTelemetryTopic(); TransportPayloadType payloadType = transportConfiguration.getTransportPayloadTypeConfiguration().getTransportPayloadType(); String payload = (payloadType == TransportPayloadType.PROTOBUF) ? " -f protobufFileName" : " -m " + PAYLOAD; - switch (credentialsType) { - case ACCESS_TOKEN: - commands.add(getMqttAccessTokenCommand(hostname, deviceCredentials) + payload); - break; - case MQTT_BASIC: - commands.add(getMqttBasicPublishCommand(hostname, deviceCredentials) + payload); - break; - case X509_CERTIFICATE: - commands.add(getMqttX509Command(hostname) + payload); - break; - } + + commands.put("mqtt", getMqttPublishCommand(mqttProperties.getDeviceConnectivityHost(), mqttProperties.getDeviceConnectivityPort(), + topicName, creds, payload)); + commands.put("mqtts", getMqttPublishCommand(mqttProperties.getDeviceConnectivityHost(), mqttProperties.getDeviceConnectivityPort(), + topicName, creds, payload)); break; case COAP: CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); CoapDeviceTypeConfiguration coapConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); if (coapConfiguration instanceof DefaultCoapDeviceTypeConfiguration) { - DefaultCoapDeviceTypeConfiguration configuration = - (DefaultCoapDeviceTypeConfiguration) coapTransportConfiguration.getCoapDeviceTypeConfiguration(); - TransportPayloadType transportPayloadType = configuration.getTransportPayloadTypeConfiguration().getTransportPayloadType(); - String payloadExample = (transportPayloadType == TransportPayloadType.PROTOBUF) ? " -t binary -f protobufFileName" : " -t json -f jsonFileName"; - commands.add(getCoapAccessTokenCommand(hostname, deviceCredentials) + payloadExample); + commands.put("coap", getCoapPublishCommand(coapProperties.getDeviceConnectivityHost(), coapProperties.getDeviceConnectivityPort(), creds)); + commands.put("coaps", getCoapPublishCommand(coapsProperties.getDeviceConnectivityHost(), coapsProperties.getDeviceConnectivityPort(), creds)); + } else if (coapConfiguration instanceof EfentoCoapDeviceTypeConfiguration) { + commands.put("coap", "Not supported"); + commands.put("coaps", "Not supported"); } break; + default: + commands.put(transportType.name(), "Not supported"); } return commands; } @@ -752,36 +799,57 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Tue, 4 Jul 2023 19:04:54 +0300 Subject: [PATCH 030/168] added tests for Geofencing filter node --- .../thingsboard/server/common/msg/TbMsg.java | 2 + .../engine/geo/AbstractGeofencingNode.java | 10 +- .../thingsboard/rule/engine/geo/GeoUtil.java | 4 +- .../filter/TbAssetTypeSwitchNodeTest.java | 5 +- .../filter/TbCheckAlarmStatusNodeTest.java | 3 +- .../engine/filter/TbCheckMessageNodeTest.java | 6 +- .../filter/TbCheckRelationNodeTest.java | 5 +- .../filter/TbDeviceTypeSwitchNodeTest.java | 2 +- .../engine/filter/TbJsFilterNodeTest.java | 6 +- .../filter/TbMsgTypeFilterNodeTest.java | 2 +- .../filter/TbMsgTypeSwitchNodeTest.java | 4 +- .../TbOriginatorTypeFilterNodeTest.java | 2 +- .../TbOriginatorTypeSwitchNodeTest.java | 4 +- .../{TbGeoUtilTest.java => GeoUtilTest.java} | 2 +- .../geo/TbGpsGeofencingFilterNodeTest.java | 460 ++++++++++++++++++ 15 files changed, 482 insertions(+), 35 deletions(-) rename rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/{TbGeoUtilTest.java => GeoUtilTest.java} (99%) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java 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 17bc7c0a8f..9848046b11 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 @@ -42,6 +42,8 @@ import java.util.UUID; @Slf4j public final class TbMsg implements Serializable { + public static final String EMPTY = "{}"; + private final String queueName; private final UUID id; private final long ts; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java index 1f1f1fd132..b263518c41 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java @@ -48,14 +48,14 @@ public abstract class AbstractGeofencingNode getConfigClazz(); protected boolean checkMatches(TbMsg msg) throws TbNodeException { - JsonElement msgDataElement = new JsonParser().parse(msg.getData()); + JsonElement msgDataElement = JsonParser.parseString(msg.getData()); if (!msgDataElement.isJsonObject()) { - throw new TbNodeException("Incoming Message is not a valid JSON object"); + throw new TbNodeException("Incoming Message is not a valid JSON object!"); } JsonObject msgDataObj = msgDataElement.getAsJsonObject(); double latitude = getValueFromMessageByName(msg, msgDataObj, config.getLatitudeKeyName()); double longitude = getValueFromMessageByName(msg, msgDataObj, config.getLongitudeKeyName()); - List perimeters = getPerimeters(msg, msgDataObj); + List perimeters = getPerimeters(msg); boolean matches = false; for (Perimeter perimeter : perimeters) { if (checkMatches(perimeter, latitude, longitude)) { @@ -74,11 +74,11 @@ public abstract class AbstractGeofencingNode getPerimeters(TbMsg msg, JsonObject msgDataObj) throws TbNodeException { + protected List getPerimeters(TbMsg msg) throws TbNodeException { if (config.isFetchPerimeterInfoFromMessageMetadata()) { if (StringUtils.isEmpty(config.getPerimeterKeyName())) { // Old configuration before "perimeterKeyName" was introduced diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java index 519a4274c1..4467dfe30b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java @@ -45,8 +45,6 @@ public class GeoUtil { private static final SpatialContext distCtx = SpatialContext.GEO; private static final JtsSpatialContext jtsCtx; - private static final JsonParser JSON_PARSER = new JsonParser(); - static { JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.normWrapLongitude = true; @@ -64,7 +62,7 @@ public class GeoUtil { throw new RuntimeException("Polygon string can't be empty or null!"); } - JsonArray polygonsJson = normalizePolygonsJson(JSON_PARSER.parse(polygonInString).getAsJsonArray()); + JsonArray polygonsJson = normalizePolygonsJson(JsonParser.parseString(polygonInString).getAsJsonArray()); List polygons = buildPolygonsFromJson(polygonsJson); Set holes = extractHolesFrom(polygons); polygons.removeIf(holes::contains); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java index a6b2433df4..772d278ee8 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java @@ -50,9 +50,6 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R class TbAssetTypeSwitchNodeTest { - private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); - private static final String EMPTY_DATA = "{}"; - private AssetId assetId; private AssetId assetIdDeleted; private TbContext ctx; @@ -121,7 +118,7 @@ class TbAssetTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, EMPTY_METADATA, EMPTY_DATA, callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java index a794b96620..7bb8365895 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java @@ -52,7 +52,6 @@ class TbCheckAlarmStatusNodeTest { private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); private static final AlarmId ALARM_ID = new AlarmId(UUID.randomUUID()); private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); - private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); private TbCheckAlarmStatusNode node; @@ -160,7 +159,7 @@ class TbCheckAlarmStatusNodeTest { } private TbMsg getTbMsg(String msgData) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, EMPTY_METADATA, msgData); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, TbMsgMetaData.EMPTY, msgData); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java index 23d6711088..8926f36054 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java @@ -46,9 +46,7 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R class TbCheckMessageNodeTest { private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); - private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); - private static final String EMPTY_DATA = "{}"; - private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, EMPTY_METADATA, EMPTY_DATA); + private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY); private TbCheckMessageNode node; @@ -195,7 +193,7 @@ class TbCheckMessageNodeTest { } private TbMsg getTbMsg(boolean emptyData) { - String data = emptyData ? EMPTY_DATA : "{\"temperature-0\": 25}"; + String data = emptyData ? TbMsg.EMPTY : "{\"temperature-0\": 25}"; var metadata = new TbMsgMetaData(); metadata.putValue(DEVICE_NAME, "Test Device"); metadata.putValue(DEVICE_TYPE, DEFAULT_DEVICE_TYPE); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java index 59b2b823bc..926d3b654b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java @@ -26,7 +26,6 @@ import org.thingsboard.rule.engine.TestDbCallbackExecutor; 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.EntityType; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -62,9 +61,7 @@ class TbCheckRelationNodeTest { private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); private static final DeviceId ORIGINATOR_ID = new DeviceId(UUID.randomUUID()); private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); - private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); - private static final String EMPTY_DATA = "{}"; - private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), ORIGINATOR_ID, EMPTY_METADATA, EMPTY_DATA); + private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), ORIGINATOR_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY); private TbCheckRelationNode node; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java index ef76787f94..3fe2e44f5d 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java @@ -118,6 +118,6 @@ class TbDeviceTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}", callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index b49dd4aac8..2f75bbfe1b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -59,7 +59,7 @@ public class TbJsFilterNodeTest { @Test public void falseEvaluationDoNotSendMsg() throws TbNodeException { initWithScript(); - TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, new TbMsgMetaData(), TbMsgDataType.JSON, TbMsg.EMPTY, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(false)); node.onMsg(ctx, msg); @@ -71,7 +71,7 @@ public class TbJsFilterNodeTest { public void exceptionInJsThrowsException() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, TbMsg.EMPTY, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFailedFuture(new ScriptException("error"))); @@ -83,7 +83,7 @@ public class TbJsFilterNodeTest { public void metadataConditionCanBeTrue() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, TbMsg.EMPTY, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java index 7814c82662..e79e2b77eb 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java @@ -97,7 +97,7 @@ class TbMsgTypeFilterNodeTest { } private TbMsg getTbMsg(EntityId entityId, TbMsgType msgType) { - return TbMsg.newMsg(msgType.name(), entityId, new TbMsgMetaData(), "{}"); + return TbMsg.newMsg(msgType.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java index d43d309cda..cd4e21182f 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java @@ -40,8 +40,6 @@ import static org.mockito.Mockito.verify; class TbMsgTypeSwitchNodeTest { private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); - private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); - private static final String EMPTY_DATA = "{}"; private TbMsgTypeSwitchNode node; @@ -90,7 +88,7 @@ class TbMsgTypeSwitchNodeTest { } private TbMsg getTbMsg(TbMsgType msgType) { - return TbMsg.newMsg(msgType.name(), DEVICE_ID, EMPTY_METADATA, EMPTY_DATA); + return TbMsg.newMsg(msgType.name(), DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java index 86ba9d1fd0..852552537f 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java @@ -96,7 +96,7 @@ class TbOriginatorTypeFilterNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}"); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java index 28d8b55264..64eb40fc41 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java @@ -42,8 +42,6 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R class TbOriginatorTypeSwitchNodeTest { private static final UUID RANDOM_UUID = UUID.randomUUID(); - private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); - private static final String EMPTY_DATA = "{}"; private TbOriginatorTypeSwitchNode node; @@ -92,7 +90,7 @@ class TbOriginatorTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, EMPTY_METADATA, EMPTY_DATA); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGeoUtilTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GeoUtilTest.java similarity index 99% rename from rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGeoUtilTest.java rename to rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GeoUtilTest.java index 6a03a8f362..f53a876954 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGeoUtilTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GeoUtilTest.java @@ -21,7 +21,7 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) -public class TbGeoUtilTest { +public class GeoUtilTest { public static final String SIMPLE_RECT = "[[51.903762928405555,23.642220786948297],[44.669801219635644,41.83345155830211]]"; public static final String SIMPLE_RECT_WITH_HOLE_IN_CENTER = "[[[44.66980121963565,23.642220786948297],[44.66980121963565,41.83345155830211],[51.903762928405555,41.83345155830211],[51.903762928405555,23.642220786948297]],[[46.10464044504632,26.234282119122227],[50.8755868028522,26.25625220459488],[51.04164771375101,38.5595000692786],[45.99790855491869,38.75723083853248]]]"; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java new file mode 100644 index 0000000000..1a01dda8c2 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java @@ -0,0 +1,460 @@ +/** + * 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.geo; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +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.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +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.thingsboard.rule.engine.geo.GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER; +import static org.thingsboard.rule.engine.geo.GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbGpsGeofencingFilterNodeTest { + + private static final double CIRCLE_RANGE = 1.0; + private static final Coordinates CIRCLE_CENTER = new Coordinates(49.0384, 31.4513); + private static final Coordinates POINT_INSIDE_CIRCLE = new Coordinates(49.0354, 31.4513); // distance from center: 0.334 km + private static final Coordinates POINT_OUTSIDE_CIRCLE = new Coordinates(49.0284, 31.4513); // distance from center: 1.112 km + + private TbContext ctx; + private TbGpsGeofencingFilterNode node; + + @BeforeEach + void setUp() { + ctx = mock(TbContext.class); + node = new TbGpsGeofencingFilterNode(); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + // Exception tests + + @Test + void givenDefaultConfig_whenOnMsg_thenExceptionInvalidMsg() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getEmptyArrayTbMsg(deviceId); + + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.onMsg(ctx, msg)); + + // THEN + assertThat(exception.getMessage()).isEqualTo("Incoming Message is not a valid JSON object!"); + } + + @Test + void givenDefaultConfig_whenOnMsg_thenExceptionMissingPerimeterDefinitionNewVersion() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, + POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.onMsg(ctx, msg)); + + // THEN + assertThat(exception.getMessage()).isEqualTo("Missing perimeter definition!"); + } + + @Test + void givenTypePolygonAndConfigWithoutPerimeterKeyName_whenOnMsg_thenExceptionMissingPerimeterDefinitionOldVersion() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterKeyName(null); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, + POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.onMsg(ctx, msg)); + + // THEN + assertThat(exception.getMessage()).isEqualTo("Missing perimeter definition!"); + } + + // Polygon tests + + @Test + void givenTypePolygonAndConfigWithoutPerimeterKeyName_whenOnMsg_thenTrue() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterKeyName(null); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForOldVersionPolygonPerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypePolygonAndConfigWithoutPerimeterKeyName_whenOnMsg_thenFalse() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterKeyName(null); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForOldVersionPolygonPerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenDefaultConfig_whenOnMsg_thenTrue() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForNewVersionPolygonPerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenDefaultConfig_whenOnMsg_thenFalse() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForNewVersionPolygonPerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypePolygonAndConfigWithPolygonDefined_whenOnMsg_thenTrue() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setFetchPerimeterInfoFromMessageMetadata(false); + config.setPolygonsDefinition(GeoUtilTest.SIMPLE_RECT); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, + POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypePolygonAndConfigWithPolygonDefined_whenOnMsg_thenFalse() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setFetchPerimeterInfoFromMessageMetadata(false); + config.setPolygonsDefinition(GeoUtilTest.SIMPLE_RECT); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, + POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + private TbMsgMetaData getMetadataForOldVersionPolygonPerimeter() { + var metadata = new TbMsgMetaData(); + metadata.putValue("perimeter", GeoUtilTest.SIMPLE_RECT); + return metadata; + } + + private TbMsgMetaData getMetadataForNewVersionPolygonPerimeter() { + var metadata = new TbMsgMetaData(); + metadata.putValue("ss_perimeter", GeoUtilTest.SIMPLE_RECT); + return metadata; + } + + // Circle tests + + @Test + void givenTypeCircleAndConfigWithoutPerimeterKeyName_whenOnMsg_thenTrue() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterKeyName(null); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForOldVersionCirclePerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_INSIDE_CIRCLE.getLatitude(), POINT_INSIDE_CIRCLE.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypeCircleAndConfigWithoutPerimeterKeyName_whenOnMsg_thenFalse() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterKeyName(null); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForOldVersionCirclePerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_OUTSIDE_CIRCLE.getLatitude(), POINT_OUTSIDE_CIRCLE.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypeCircle_whenOnMsg_thenTrue() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterType(PerimeterType.CIRCLE); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForNewVersionCirclePerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_INSIDE_CIRCLE.getLatitude(), POINT_INSIDE_CIRCLE.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypeCircle_whenOnMsg_thenFalse() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterType(PerimeterType.CIRCLE); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForNewVersionCirclePerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_OUTSIDE_CIRCLE.getLatitude(), POINT_OUTSIDE_CIRCLE.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypeCircleAndConfigWithCircleDefined_whenOnMsg_thenTrue() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setFetchPerimeterInfoFromMessageMetadata(false); + config.setPerimeterType(PerimeterType.CIRCLE); + config.setCenterLatitude(CIRCLE_CENTER.getLatitude()); + config.setCenterLongitude(CIRCLE_CENTER.getLongitude()); + config.setRange(CIRCLE_RANGE); + config.setRangeUnit(RangeUnit.KILOMETER); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, + POINT_INSIDE_CIRCLE.getLatitude(), POINT_INSIDE_CIRCLE.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypeCircleAndConfigWithCircleDefined_whenOnMsg_thenFalse() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setFetchPerimeterInfoFromMessageMetadata(false); + config.setPerimeterType(PerimeterType.CIRCLE); + config.setCenterLatitude(CIRCLE_CENTER.getLatitude()); + config.setCenterLongitude(CIRCLE_CENTER.getLongitude()); + config.setRange(CIRCLE_RANGE); + config.setRangeUnit(RangeUnit.KILOMETER); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, + POINT_OUTSIDE_CIRCLE.getLatitude(), POINT_OUTSIDE_CIRCLE.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + private TbMsgMetaData getMetadataForOldVersionCirclePerimeter() { + var metadata = new TbMsgMetaData(); + metadata.putValue("centerLatitude", String.valueOf(CIRCLE_CENTER.getLatitude())); + metadata.putValue("centerLongitude", String.valueOf(CIRCLE_CENTER.getLongitude())); + metadata.putValue("range", String.valueOf(CIRCLE_RANGE)); + metadata.putValue("rangeUnit", String.valueOf(RangeUnit.KILOMETER)); + return metadata; + } + + private TbMsgMetaData getMetadataForNewVersionCirclePerimeter() { + ObjectNode perimeter = JacksonUtil.newObjectNode(); + perimeter.put("latitude", CIRCLE_CENTER.getLatitude()); + perimeter.put("longitude", CIRCLE_CENTER.getLongitude()); + perimeter.put("radius", CIRCLE_RANGE); + perimeter.put("radiusUnit", String.valueOf(RangeUnit.KILOMETER)); + var metadata = new TbMsgMetaData(); + metadata.putValue("ss_perimeter", JacksonUtil.toString(perimeter)); + return metadata; + } + + private TbMsg getTbMsg(EntityId entityId, TbMsgMetaData metadata, double latitude, double longitude) { + String data = "{\"latitude\": " + latitude + ", \"longitude\": " + longitude + "}"; + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, metadata, data); + } + + private TbMsg getEmptyArrayTbMsg(EntityId entityId) { + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, "[]"); + } + +} From 3baa12ce7739bfec4e66bb0c3392f64fccd0e720 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 5 Jul 2023 15:06:42 +0300 Subject: [PATCH 031/168] refactored config properties --- .../src/main/resources/thingsboard.yml | 35 ++-- .../DeviceConnectivityConfiguration.java | 24 ++- .../dao/device/DeviceConnectivityInfo.java | 26 +++ .../server/dao/device/DeviceServiceImpl.java | 168 +++++++++--------- 4 files changed, 152 insertions(+), 101 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 1c044daa74..f8ea15b2b8 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -775,10 +775,6 @@ transport: worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}" max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}" so_keep_alive: "${NETTY_SO_KEEPALIVE:false}" - # Mqtt device connectivity host to publish telemetry - device_connectivity_host: "${MQTT_DEVICE_CONNECTIVITY_HOST:localhost}" - # Mqtt device connectivity port to publish telemetry - device_connectivity_port: "${MQTT_DEVICE_CONNECTIVITY_PORT:1883}" # MQTT SSL configuration ssl: # Enable/disable SSL support @@ -789,10 +785,6 @@ transport: bind_port: "${MQTT_SSL_BIND_PORT:8883}" # SSL protocol: See https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#sslcontext-algorithms protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}" - # Mqtt ssl device connectivity host to publish telemetry - device_connectivity_host: "${MQTT_DEVICE_CONNECTIVITY_HOST:localhost}" - # Mqtt ssl device connectivity port to publish telemetry - device_connectivity_port: "${MQTT_DEVICE_CONNECTIVITY_PORT:8883}" # Server SSL credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -829,10 +821,6 @@ transport: piggyback_timeout: "${COAP_PIGGYBACK_TIMEOUT:500}" psm_activity_timer: "${COAP_PSM_ACTIVITY_TIMER:10000}" paging_transmission_window: "${COAP_PAGING_TRANSMISSION_WINDOW:10000}" - # Coap device connectivity host to publish telemetry - device_connectivity_host: "${COAP_DEVICE_CONNECTIVITY_HOST:localhost}" - # Coap device connectivity port to publish telemetry - device_connectivity_port: "${COAP_DEVICE_CONNECTIVITY_PORT:5683}" dtls: # Enable/disable DTLS 1.2 support enabled: "${COAP_DTLS_ENABLED:false}" @@ -842,10 +830,6 @@ transport: bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" - # Coap DTLS device connectivity host to publish telemetry - device_connectivity_host: "${COAP_DEVICE_CONNECTIVITY_HOST:localhost}" - # Coap DTLS device connectivity port to publish telemetry - device_connectivity_port: "${COAP_DEVICE_CONNECTIVITY_PORT:5684}" # Server DTLS credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -994,6 +978,25 @@ transport: enabled: "${TB_TRANSPORT_STATS_ENABLED:true}" print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}" +# Device connectivity properties to publish telemetry +device: + connectivity: + http: + host: "${DEVICE_CONNECTIVITY_HTTP_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_HTTP_PORT:8080}" + mqtt: + host: "${DEVICE_CONNECTIVITY_MQTT_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_MQTT_PORT:1883}" + mqtts: + host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_MQTTS_PORT:8883}" + coap: + host: "${DEVICE_CONNECTIVITY_COAP_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_COAP_PORT:5683}" + coaps: + host: "${DEVICE_CONNECTIVITY_COAPS_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_COAPS_PORT:5684}" + # Edges parameters edges: enabled: "${EDGES_ENABLED:true}" diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java index f156729cbc..454c795f12 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java @@ -1,9 +1,29 @@ +/** + * 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.dao.device; import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import java.util.Map; + +@Configuration +@ConfigurationProperties(prefix = "device") @Data public class DeviceConnectivityConfiguration { - private String deviceConnectivityHost; - private Integer deviceConnectivityPort; + private Map connectivity; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java new file mode 100644 index 0000000000..7b477bfc42 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java @@ -0,0 +1,26 @@ +/** + * 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.dao.device; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + + +@Data +public class DeviceConnectivityInfo { + private String host; + private Integer port; +} 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 cca89742e1..fe5ac33e73 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 @@ -20,9 +20,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; @@ -108,7 +105,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService commands.put("http", v)); + Optional.ofNullable(getMqttPublishCommand(creds)).ifPresent(v -> commands.put("mqtt", v)); + Optional.ofNullable(getMqttsPublishCommand(creds)).ifPresent(v -> commands.put("mqtts", v)); + Optional.ofNullable(getCoapPublishCommand(creds)).ifPresent(v -> commands.put("coap", v)); + Optional.ofNullable(getCoapsPublishCommand(creds)).ifPresent(v -> commands.put("coaps", v)); break; case MQTT: MqttDeviceProfileTransportConfiguration transportConfiguration = @@ -217,25 +164,22 @@ public class DeviceServiceImpl extends AbstractCachedEntityService commands.put("mqtt", v)); + Optional.ofNullable(getMqttsPublishCommand(topicName, creds, payload)).ifPresent(v -> commands.put("mqtts", v)); break; case COAP: CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); CoapDeviceTypeConfiguration coapConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); if (coapConfiguration instanceof DefaultCoapDeviceTypeConfiguration) { - commands.put("coap", getCoapPublishCommand(coapProperties.getDeviceConnectivityHost(), coapProperties.getDeviceConnectivityPort(), creds)); - commands.put("coaps", getCoapPublishCommand(coapsProperties.getDeviceConnectivityHost(), coapsProperties.getDeviceConnectivityPort(), creds)); + Optional.ofNullable(getCoapPublishCommand(creds)).ifPresent(v -> commands.put("coap", v)); + Optional.ofNullable(getCoapsPublishCommand(creds)).ifPresent(v -> commands.put("coaps", v)); } else if (coapConfiguration instanceof EfentoCoapDeviceTypeConfiguration) { - commands.put("coap", "Not supported"); - commands.put("coaps", "Not supported"); + commands.put("coap for efento", "Not supported"); } break; default: - commands.put(transportType.name(), "Not supported"); + commands.put(transportType.name(), NOT_SUPPORTED); } return commands; } @@ -800,18 +744,61 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Wed, 5 Jul 2023 15:27:52 +0300 Subject: [PATCH 032/168] minor refactoring --- application/src/main/resources/thingsboard.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f8ea15b2b8..2f58590bf6 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -981,9 +981,6 @@ transport: # Device connectivity properties to publish telemetry device: connectivity: - http: - host: "${DEVICE_CONNECTIVITY_HTTP_HOST:localhost}" - port: "${DEVICE_CONNECTIVITY_HTTP_PORT:8080}" mqtt: host: "${DEVICE_CONNECTIVITY_MQTT_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_MQTT_PORT:1883}" From 47929ef78442808c32b88fa627426b5f98367a7e Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 6 Jul 2023 12:20:23 +0300 Subject: [PATCH 033/168] replaced newMsg and trasformMsg with new methods that uses TbMsgType && mark old methods as deprecated && refactoring --- .../actors/ruleChain/DefaultTbContext.java | 58 +++++-- .../server/controller/RpcV2Controller.java | 4 +- .../service/action/EntityActionService.java | 2 +- .../device/DeviceProvisionServiceImpl.java | 29 ++-- .../service/edge/rpc/EdgeGrpcService.java | 15 +- .../processor/device/DeviceEdgeProcessor.java | 9 +- .../telemetry/BaseTelemetryProcessor.java | 11 +- .../DefaultTbNotificationEntityService.java | 5 +- .../rpc/DefaultTbCoreDeviceRpcService.java | 5 +- .../server/service/rpc/TbRpcService.java | 3 +- .../state/DefaultDeviceStateService.java | 21 +-- .../transport/DefaultTransportApiService.java | 4 +- .../AbstractRuleEngineControllerTest.java | 6 +- ...AbstractRuleEngineFlowIntegrationTest.java | 13 +- ...actRuleEngineLifecycleIntegrationTest.java | 7 +- .../SequentialTimeseriesPersistenceTest.java | 4 +- .../server/common/data/msg/TbMsgType.java | 32 +++- .../server/common/data/msg/TbMsgTypeTest.java | 18 +- .../thingsboard/server/common/msg/TbMsg.java | 164 +++++++++++++++++- .../service/DefaultTransportService.java | 2 +- .../rule/engine/api/TbContext.java | 32 +++- .../rule/engine/api/util/TbNodeUtilsTest.java | 11 +- .../engine/action/TbAbstractAlarmNode.java | 21 +-- .../rule/engine/action/TbClearAlarmNode.java | 2 +- .../rule/engine/action/TbCreateAlarmNode.java | 4 +- .../engine/action/TbCreateRelationNode.java | 2 +- .../rule/engine/action/TbMsgCountNode.java | 15 +- .../rule/engine/aws/sns/TbSnsNode.java | 4 +- .../rule/engine/aws/sqs/TbSqsNode.java | 17 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 13 +- .../deduplication/TbMsgDeduplicationNode.java | 8 +- .../rule/engine/delay/TbMsgDelayNode.java | 12 +- .../rule/engine/gcp/pubsub/TbPubSubNode.java | 13 +- .../rule/engine/kafka/TbKafkaNode.java | 12 +- .../rule/engine/mail/TbMsgToEmailNode.java | 11 +- .../rule/engine/mail/TbSendEmailNode.java | 8 +- .../engine/metadata/CalculateDeltaNode.java | 2 +- .../engine/metadata/TbGetTelemetryNode.java | 18 +- .../rule/engine/mqtt/TbMqttNode.java | 10 +- .../rule/engine/profile/AlarmState.java | 3 +- .../rule/engine/profile/DeviceState.java | 6 +- .../engine/profile/TbDeviceProfileNode.java | 61 ++++--- .../rule/engine/rabbitmq/TbRabbitMqNode.java | 7 +- .../rule/engine/rest/TbHttpClient.java | 16 +- .../rule/engine/rest/TbRestApiCallNode.java | 1 - .../rule/engine/rpc/TbSendRPCRequestNode.java | 5 +- .../transform/TbChangeOriginatorNode.java | 10 +- .../rule/engine/transform/TbCopyKeysNode.java | 2 +- .../engine/transform/TbDeleteKeysNode.java | 4 +- .../rule/engine/transform/TbJsonPathNode.java | 2 +- .../engine/transform/TbRenameKeysNode.java | 4 +- .../engine/transform/TbSplitArrayMsgNode.java | 4 +- .../rule/engine/action/TbAlarmNodeTest.java | 110 ++++++------ .../action/TbCreateRelationNodeTest.java | 36 ++-- .../rule/engine/action/TbLogNodeTest.java | 7 +- .../engine/edge/TbMsgPushToEdgeNodeTest.java | 25 ++- .../filter/TbAssetTypeSwitchNodeTest.java | 4 +- .../filter/TbCheckAlarmStatusNodeTest.java | 4 +- .../engine/filter/TbCheckMessageNodeTest.java | 16 +- .../filter/TbCheckRelationNodeTest.java | 4 +- .../filter/TbDeviceTypeSwitchNodeTest.java | 4 +- .../engine/filter/TbJsFilterNodeTest.java | 10 +- .../engine/filter/TbJsSwitchNodeTest.java | 3 +- .../filter/TbMsgTypeFilterNodeTest.java | 2 +- .../filter/TbMsgTypeSwitchNodeTest.java | 2 +- .../TbOriginatorTypeFilterNodeTest.java | 4 +- .../TbOriginatorTypeSwitchNodeTest.java | 4 +- .../geo/TbGpsGeofencingFilterNodeTest.java | 24 ++- .../engine/mail/TbMsgToEmailNodeTest.java | 9 +- .../rule/engine/math/TbMathNodeTest.java | 31 ++-- .../metadata/CalculateDeltaNodeTest.java | 29 ++-- .../TbFetchDeviceCredentialsNodeTest.java | 8 +- .../metadata/TbGetAttributesNodeTest.java | 5 +- .../TbGetCustomerAttributeNodeTest.java | 14 +- .../TbGetCustomerDetailsNodeTest.java | 6 +- .../TbGetOriginatorFieldsNodeTest.java | 14 +- .../TbGetRelatedAttributeNodeTest.java | 12 +- .../TbGetTenantAttributeNodeTest.java | 12 +- .../metadata/TbGetTenantDetailsNodeTest.java | 6 +- .../rule/engine/profile/DeviceStateTest.java | 24 ++- .../profile/TbDeviceProfileNodeTest.java | 118 ++++++------- .../rule/engine/rest/TbHttpClientTest.java | 13 +- .../engine/rest/TbRestApiCallNodeTest.java | 23 +-- .../engine/rpc/TbSendRPCReplyNodeTest.java | 4 +- .../TbMsgDeleteAttributesNodeTest.java | 17 +- .../transform/TbChangeOriginatorNodeTest.java | 17 +- .../engine/transform/TbCopyKeysNodeTest.java | 4 +- .../transform/TbDeleteKeysNodeTest.java | 4 +- .../engine/transform/TbJsonPathNodeTest.java | 4 +- .../transform/TbMsgDeduplicationNodeTest.java | 15 +- .../transform/TbRenameKeysNodeTest.java | 4 +- .../transform/TbSplitArrayMsgNodeTest.java | 4 +- .../transform/TbTransformMsgNodeTest.java | 7 +- 93 files changed, 794 insertions(+), 621 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 f9d1940714..dc03a97254 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 @@ -59,6 +59,7 @@ 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.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -345,8 +346,33 @@ class DefaultTbContext implements TbContext { return TbMsg.transformMsg(origMsg, type, originator, metaData, data); } + @Override + public TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { + return newMsg(queueName, type, originator, null, metaData, data); + } + + @Override + public TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { + return TbMsg.newMsg(queueName, type, originator, customerId, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId()); + } + + @Override + public TbMsg transformMsg(TbMsg origMsg, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { + return TbMsg.transformMsg(origMsg, type, originator, metaData, data); + } + + @Override + public TbMsg transformMsg(TbMsg origMsg, TbMsgMetaData metaData, String data) { + return TbMsg.transformMsg(origMsg, metaData, data); + } + + @Override + public TbMsg transformMsgOriginator(TbMsg origMsg, EntityId originator) { + return TbMsg.transformMsgOriginator(origMsg, originator); + } + public TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId) { - return entityActionMsg(customer, customer.getId(), ruleNodeId, ENTITY_CREATED.name()); + return entityActionMsg(customer, customer.getId(), ruleNodeId, ENTITY_CREATED); } public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { @@ -354,7 +380,7 @@ class DefaultTbContext implements TbContext { if (device.getDeviceProfileId() != null) { deviceProfile = mainCtx.getDeviceProfileCache().find(device.getDeviceProfileId()); } - return entityActionMsg(device, device.getId(), ruleNodeId, ENTITY_CREATED.name(), deviceProfile); + return entityActionMsg(device, device.getId(), ruleNodeId, ENTITY_CREATED, deviceProfile); } public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { @@ -362,10 +388,10 @@ class DefaultTbContext implements TbContext { if (asset.getAssetProfileId() != null) { assetProfile = mainCtx.getAssetProfileCache().find(asset.getAssetProfileId()); } - return entityActionMsg(asset, asset.getId(), ruleNodeId, ENTITY_CREATED.name(), assetProfile); + return entityActionMsg(asset, asset.getId(), ruleNodeId, ENTITY_CREATED, assetProfile); } - public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { + public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, TbMsgType actionMsgType) { HasRuleEngineProfile profile = null; if (EntityType.DEVICE.equals(alarm.getOriginator().getEntityType())) { DeviceId deviceId = new DeviceId(alarm.getOriginator().getId()); @@ -374,7 +400,7 @@ class DefaultTbContext implements TbContext { AssetId assetId = new AssetId(alarm.getOriginator().getId()); profile = mainCtx.getAssetProfileCache().get(getTenantId(), assetId); } - return entityActionMsg(alarm, alarm.getOriginator(), ruleNodeId, action, profile); + return entityActionMsg(alarm, alarm.getOriginator(), ruleNodeId, actionMsgType, profile); } public TbMsg attributesUpdatedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List attributes) { @@ -382,7 +408,7 @@ class DefaultTbContext implements TbContext { if (attributes != null) { attributes.forEach(attributeKvEntry -> JacksonUtil.addKvEntry(entityNode, attributeKvEntry)); } - return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_UPDATED.name(), JacksonUtil.toString(entityNode)); + return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_UPDATED, JacksonUtil.toString(entityNode)); } public TbMsg attributesDeletedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List keys) { @@ -391,10 +417,10 @@ class DefaultTbContext implements TbContext { if (keys != null) { keys.forEach(attrsArrayNode::add); } - return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_DELETED.name(), JacksonUtil.toString(entityNode)); + return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_DELETED, JacksonUtil.toString(entityNode)); } - private TbMsg attributesActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, String action, String msgData) { + private TbMsg attributesActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, TbMsgType actionMsgType, String msgData) { TbMsgMetaData tbMsgMetaData = getActionMetaData(ruleNodeId); tbMsgMetaData.putValue("scope", scope); HasRuleEngineProfile profile = null; @@ -405,7 +431,7 @@ class DefaultTbContext implements TbContext { AssetId assetId = new AssetId(originator.getId()); profile = mainCtx.getAssetProfileCache().get(getTenantId(), assetId); } - return entityActionMsg(originator, tbMsgMetaData, msgData, action, profile); + return entityActionMsg(originator, tbMsgMetaData, msgData, actionMsgType, profile); } @Override @@ -413,26 +439,26 @@ class DefaultTbContext implements TbContext { mainCtx.getClusterService().onEdgeEventUpdate(tenantId, edgeId); } - public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action) { - return entityActionMsg(entity, id, ruleNodeId, action, null); + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, TbMsgType actionMsgType) { + return entityActionMsg(entity, id, ruleNodeId, actionMsgType, null); } - public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action, K profile) { + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, TbMsgType actionMsgType, K profile) { try { - return entityActionMsg(id, getActionMetaData(ruleNodeId), JacksonUtil.toString(JacksonUtil.valueToTree(entity)), action, profile); + return entityActionMsg(id, getActionMetaData(ruleNodeId), JacksonUtil.toString(JacksonUtil.valueToTree(entity)), actionMsgType, profile); } catch (IllegalArgumentException e) { - throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e); + throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + actionMsgType.name() + " msg: " + e); } } - private TbMsg entityActionMsg(I id, TbMsgMetaData msgMetaData, String msgData, String action, K profile) { + private TbMsg entityActionMsg(I id, TbMsgMetaData msgMetaData, String msgData, TbMsgType actionMsgType, K profile) { String defaultQueueName = null; RuleChainId defaultRuleChainId = null; if (profile != null) { defaultQueueName = profile.getDefaultQueueName(); defaultRuleChainId = profile.getDefaultRuleChainId(); } - return TbMsg.newMsg(defaultQueueName, action, id, msgMetaData, msgData, defaultRuleChainId, null); + return TbMsg.newMsg(defaultQueueName, actionMsgType, id, msgMetaData, msgData, defaultRuleChainId, null); } @Override diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java index d65f87497d..451a5b7dc0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rpc.Rpc; @@ -52,7 +53,6 @@ import org.thingsboard.server.service.security.permission.Operation; import javax.annotation.Nullable; import java.util.UUID; -import static org.thingsboard.server.common.data.msg.TbMsgType.RPC_DELETED; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END; @@ -239,7 +239,7 @@ public class RpcV2Controller extends AbstractRpcController { rpcService.deleteRpc(getTenantId(), rpcId); rpc.setStatus(RpcStatus.DELETED); - TbMsg msg = TbMsg.newMsg(RPC_DELETED.name(), rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_DELETED, rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); tbClusterService.pushMsgToRuleEngine(getTenantId(), rpc.getDeviceId(), msg, null); } } diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java index cea4a6922a..6c855a89c9 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java +++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java @@ -171,7 +171,7 @@ public class EntityActionService { if (tenantId != null && !tenantId.isSysTenantId()) { processNotificationRules(tenantId, entityId, entity, actionType, user, additionalInfo); } - TbMsg tbMsg = TbMsg.newMsg(msgType.get().name(), entityId, customerId, metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); + TbMsg tbMsg = TbMsg.newMsg(msgType.get(), entityId, customerId, metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null); } catch (Exception e) { log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e); diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index d5614c2193..763ce01116 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; @@ -35,6 +36,7 @@ import org.thingsboard.server.common.data.id.UserId; 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.msg.TbMsgType; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.msg.TbMsg; @@ -68,11 +70,6 @@ import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; -import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_FAILURE; -import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_SUCCESS; - @Service @Slf4j @@ -166,7 +163,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) { if (targetDevice != null) { log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); - notify(targetDevice, provisionRequest, PROVISION_FAILURE.name(), false); + notify(targetDevice, provisionRequest, TbMsgType.PROVISION_FAILURE, false); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } else { return createDevice(provisionRequest, targetProfile); @@ -192,13 +189,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private ProvisionResponse processProvision(Device device, ProvisionRequest provisionRequest) { try { Optional provisionState = attributesService.find(device.getTenantId(), device.getId(), - SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); + DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); if (provisionState != null && provisionState.isPresent() && !provisionState.get().getValueAsString().equals(PROVISIONED_STATE)) { - notify(device, provisionRequest, PROVISION_FAILURE.name(), false); + notify(device, provisionRequest, TbMsgType.PROVISION_FAILURE, false); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } else { saveProvisionStateAttribute(device).get(); - notify(device, provisionRequest, PROVISION_SUCCESS.name(), true); + notify(device, provisionRequest, TbMsgType.PROVISION_SUCCESS, true); } } catch (InterruptedException | ExecutionException e) { throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); @@ -210,7 +207,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return processCreateDevice(provisionRequest, profile); } - private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) { + private void notify(Device device, ProvisionRequest provisionRequest, TbMsgType type, boolean success) { pushProvisionEventToRuleEngine(provisionRequest, device, type); logAction(device.getTenantId(), device.getCustomerId(), device, success, provisionRequest); } @@ -226,14 +223,14 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { clusterService.onDeviceUpdated(savedDevice, null); saveProvisionStateAttribute(savedDevice).get(); pushDeviceCreatedEventToRuleEngine(savedDevice); - notify(savedDevice, provisionRequest, PROVISION_SUCCESS.name(), true); + notify(savedDevice, provisionRequest, TbMsgType.PROVISION_SUCCESS, true); return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS); } catch (Exception e) { log.warn("[{}] Error during device creation from provision request: [{}]", provisionRequest.getDeviceName(), provisionRequest, e); Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); if (device != null) { - notify(device, provisionRequest, PROVISION_FAILURE.name(), false); + notify(device, provisionRequest, TbMsgType.PROVISION_FAILURE, false); } throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } @@ -248,7 +245,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } private ListenableFuture> saveProvisionStateAttribute(Device device) { - return attributesService.save(device.getTenantId(), device.getId(), SERVER_SCOPE, + return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE), System.currentTimeMillis()))); } @@ -257,7 +254,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); } - private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) { + private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, TbMsgType type) { try { JsonNode entityNode = JacksonUtil.valueToTree(request); TbMsg msg = TbMsg.newMsg(type, device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.toString(entityNode)); @@ -270,10 +267,10 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private void pushDeviceCreatedEventToRuleEngine(Device device) { try { ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); - TbMsg msg = TbMsg.newMsg(ENTITY_CREATED.name(), device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); + TbMsg msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); sendToRuleEngine(device.getTenantId(), msg, null); } catch (JsonProcessingException | IllegalArgumentException e) { - log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), ENTITY_CREATED.name(), e); + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), TbMsgType.ENTITY_CREATED.name(), e); } } 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 233dcba2cc..73f968e05d 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 @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.id.TenantId; 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.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -71,10 +72,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; - @Service @Slf4j @ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true") @@ -278,7 +275,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, true); long lastConnectTs = System.currentTimeMillis(); save(edgeId, DefaultDeviceStateService.LAST_CONNECT_TIME, lastConnectTs); - pushRuleEngineMessage(edgeGrpcSession.getEdge().getTenantId(), edgeId, lastConnectTs, CONNECT_EVENT.name()); + pushRuleEngineMessage(edgeGrpcSession.getEdge().getTenantId(), edgeId, lastConnectTs, TbMsgType.CONNECT_EVENT); cancelScheduleEdgeEventsCheck(edgeId); scheduleEdgeEventsCheck(edgeGrpcSession); } @@ -395,7 +392,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, false); long lastDisconnectTs = System.currentTimeMillis(); save(edgeId, DefaultDeviceStateService.LAST_DISCONNECT_TIME, lastDisconnectTs); - pushRuleEngineMessage(toRemove.getEdge().getTenantId(), edgeId, lastDisconnectTs, DISCONNECT_EVENT.name()); + pushRuleEngineMessage(toRemove.getEdge().getTenantId(), edgeId, 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); @@ -448,10 +445,10 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i } } - private void pushRuleEngineMessage(TenantId tenantId, EdgeId edgeId, long ts, String msgType) { + private void pushRuleEngineMessage(TenantId tenantId, EdgeId edgeId, long ts, TbMsgType msgType) { try { ObjectNode edgeState = JacksonUtil.newObjectNode(); - if (msgType.equals(CONNECT_EVENT.name())) { + if (msgType.equals(TbMsgType.CONNECT_EVENT)) { edgeState.put(DefaultDeviceStateService.ACTIVITY_STATE, true); edgeState.put(DefaultDeviceStateService.LAST_CONNECT_TIME, ts); } else { @@ -461,7 +458,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i String data = JacksonUtil.toString(edgeState); TbMsgMetaData md = new TbMsgMetaData(); if (!persistToTelemetry) { - md.putValue(DataConstants.SCOPE, SERVER_SCOPE); + md.putValue(DataConstants.SCOPE, DataConstants.SERVER_SCOPE); } 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/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index 49f2e0ead2..67194cb618 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -62,9 +62,6 @@ import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import java.util.UUID; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; -import static org.thingsboard.server.common.data.msg.TbMsgType.TO_SERVER_RPC_REQUEST; - @Component @Slf4j @TbCoreComponent @@ -127,7 +124,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { try { Device device = deviceService.findDeviceById(tenantId, deviceId); ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); - TbMsg tbMsg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, device.getCustomerId(), + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, device.getCustomerId(), getActionTbMsgMetaData(edge, device.getCustomerId()), TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override @@ -141,7 +138,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { } }); } catch (JsonProcessingException | IllegalArgumentException e) { - log.warn("[{}] Failed to push device action to rule engine: {}", deviceId, ENTITY_CREATED.name(), e); + log.warn("[{}] Failed to push device action to rule engine: {}", deviceId, TbMsgType.ENTITY_CREATED.name(), e); } } @@ -219,7 +216,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { ObjectNode data = JacksonUtil.newObjectNode(); data.put("method", deviceRpcCallMsg.getRequestMsg().getMethod()); data.put("params", deviceRpcCallMsg.getRequestMsg().getParams()); - TbMsg tbMsg = TbMsg.newMsg(TO_SERVER_RPC_REQUEST.name(), deviceId, null, metaData, + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST, deviceId, null, metaData, TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(data)); tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index c55a931194..5942628bf3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -50,6 +50,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +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.ServiceType; @@ -72,10 +73,6 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; -import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; - @Slf4j public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { @@ -187,7 +184,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); metaData.putValue("ts", tsKv.getTs() + ""); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), POST_TELEMETRY_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.POST_TELEMETRY_REQUEST, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { @@ -231,7 +228,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { SettableFuture futureToSet = SettableFuture.create(); JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), POST_ATTRIBUTES_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { @@ -260,7 +257,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { @Override public void onSuccess(@Nullable Void tmp) { var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), ATTRIBUTES_UPDATED.name(), entityId, + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.ATTRIBUTES_UPDATED, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java index 719158403c..68b9cdecbb 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.rule.RuleChain; @@ -52,8 +53,6 @@ import org.thingsboard.server.service.gateway_device.GatewayNotificationsService import java.util.List; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED_FROM_TENANT; - @Slf4j @Service @RequiredArgsConstructor @@ -287,7 +286,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { String data = JacksonUtil.toString(JacksonUtil.valueToTree(assignedDevice)); if (data != null) { - TbMsg tbMsg = TbMsg.newMsg(ENTITY_ASSIGNED_FROM_TENANT.name(), assignedDevice.getId(), + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null); } diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java index 2ec8f09bd0..8ea7208c55 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java @@ -26,6 +26,7 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -48,8 +49,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import static org.thingsboard.server.common.data.msg.TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE; - /** * Created by ashvayka on 27.03.18. */ @@ -183,7 +182,7 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { entityNode.put(DataConstants.ADDITIONAL_INFO, msg.getAdditionalInfo()); try { - TbMsg tbMsg = TbMsg.newMsg(RPC_CALL_FROM_SERVER_TO_DEVICE.name(), msg.getDeviceId(), Optional.ofNullable(currentUser).map(User::getCustomerId).orElse(null), metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), Optional.ofNullable(currentUser).map(User::getCustomerId).orElse(null), metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null); } catch (IllegalArgumentException e) { throw new RuntimeException(e); diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java index ad40176cef..8c6f0d768c 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java @@ -24,6 +24,7 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rpc.Rpc; @@ -62,7 +63,7 @@ public class TbRpcService { } private void pushRpcMsgToRuleEngine(TenantId tenantId, Rpc rpc) { - TbMsg msg = TbMsg.newMsg("RPC_" + rpc.getStatus().name(), rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); + TbMsg msg = TbMsg.newMsg(TbMsgType.valueOf("RPC_" + rpc.getStatus().name()), rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); tbClusterService.pushMsgToRuleEngine(tenantId, rpc.getDeviceId(), msg, null); } 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 965b781fe9..05d71c4fca 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 @@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityTrigger; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterable; @@ -102,12 +103,6 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; - /** * Created by ashvayka on 01.05.18. */ @@ -229,7 +224,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService { attributes.flatMap(KvEntry::getLongValue).ifPresent((inactivityTimeout) -> { if (inactivityTimeout > 0) { @@ -771,11 +766,11 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService filterByCustomEvent() { - return event -> event.getBody().get("msgType").textValue().equals("CUSTOM"); + protected Predicate filterByPostTelemetryEventType() { + return event -> event.getBody().get("msgType").textValue().equals(TbMsgType.POST_TELEMETRY_REQUEST.name()); } } diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java index c448c4220f..e626adf5f4 100644 --- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.event.Event; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.rule.NodeConnectionInfo; @@ -180,14 +181,14 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true); - TbMsg tbMsg = TbMsg.newMsg("CUSTOM", device.getId(), new TbMsgMetaData(), "{}", tbMsgCallback); + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, tbMsgCallback); QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null); // Pushing Message to the system actorSystem.tell(qMsg); Mockito.verify(tbMsgCallback, Mockito.timeout(10000)).onSuccess(); PageData eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000); - List events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); + List events = eventsPage.getData().stream().filter(filterByPostTelemetryEventType()).collect(Collectors.toList()); Assert.assertEquals(2, events.size()); EventInfo inEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get(); @@ -204,7 +205,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule RuleNode lastRuleNode = metaData.getNodes().stream().filter(node -> !node.getId().equals(finalRuleChain.getFirstRuleNodeId())).findFirst().get(); eventsPage = getDebugEvents(savedTenant.getId(), lastRuleNode.getId(), 1000); - events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); + events = eventsPage.getData().stream().filter(filterByPostTelemetryEventType()).collect(Collectors.toList()); Assert.assertEquals(2, events.size()); @@ -305,7 +306,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true); - TbMsg tbMsg = TbMsg.newMsg("CUSTOM", device.getId(), new TbMsgMetaData(), "{}", tbMsgCallback); + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, tbMsgCallback); QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null); // Pushing Message to the system actorSystem.tell(qMsg); @@ -313,7 +314,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule Mockito.verify(tbMsgCallback, Mockito.timeout(10000)).onSuccess(); PageData eventsPage = getDebugEvents(savedTenant.getId(), rootRuleChain.getFirstRuleNodeId(), 1000); - List events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); + List events = eventsPage.getData().stream().filter(filterByPostTelemetryEventType()).collect(Collectors.toList()); Assert.assertEquals(2, events.size()); @@ -331,7 +332,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule RuleNode lastRuleNode = secondaryMetaData.getNodes().stream().filter(node -> !node.getId().equals(finalRuleChain.getFirstRuleNodeId())).findFirst().get(); eventsPage = getDebugEvents(savedTenant.getId(), lastRuleNode.getId(), 1000); - events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); + events = eventsPage.getData().stream().filter(filterByPostTelemetryEventType()).collect(Collectors.toList()); Assert.assertEquals(2, events.size()); diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java index 9753f06aff..6216f993dc 100644 --- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.EventInfo; import org.thingsboard.server.common.data.event.EventType; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; @@ -139,7 +140,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac log.warn("attr updated"); TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true); - TbMsg tbMsg = TbMsg.newMsg("CUSTOM", device.getId(), new TbMsgMetaData(), "{}", tbMsgCallback); + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, tbMsgCallback); QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(tenantId, tbMsg, null, null); // Pushing Message to the system log.warn("before tell tbMsgCallback"); @@ -147,12 +148,12 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac log.warn("awaiting tbMsgCallback"); Mockito.verify(tbMsgCallback, Mockito.timeout(TimeUnit.SECONDS.toMillis(TIMEOUT))).onSuccess(); log.warn("awaiting events"); - List events = Awaitility.await("get debug by custom event") + List events = Awaitility.await("get debug by post telemetry event") .pollInterval(10, MILLISECONDS) .atMost(TIMEOUT, TimeUnit.SECONDS) .until(() -> { List debugEvents = getDebugEvents(tenantId, ruleChainFinal.getFirstRuleNodeId(), 1000) - .getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); + .getData().stream().filter(filterByPostTelemetryEventType()).collect(Collectors.toList()); log.warn("filtered debug events [{}]", debugEvents.size()); debugEvents.forEach((e) -> log.warn("event: {}", e)); return debugEvents; diff --git a/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java b/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java index 6c060feb14..654f934ed2 100644 --- a/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -50,7 +51,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @DaoSqlTest public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest { @@ -133,7 +133,7 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest void saveLatestTsForAssetAndDevice(List devices, Asset asset, int idx) throws ExecutionException, InterruptedException, TimeoutException { for (Device device : devices) { - TbMsg tbMsg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), getTbMsgMetadata(device.getName(), ts.get(idx)), TbMsgDataType.JSON, 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 1872fd676f..0084b391eb 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 @@ -66,16 +66,44 @@ public enum TbMsgType { RELATION_DELETED("Relation Deleted"), RELATIONS_DELETED("All Relations Deleted"), PROVISION_SUCCESS(null), - PROVISION_FAILURE(null); + PROVISION_FAILURE(null), + SEND_EMAIL(null), + + // tellSelfOnly types + GENERATOR_NODE_SELF_MSG(null, true), + + DEVICE_PROFILE_PERIODIC_SELF_MSG(null, true), + DEVICE_PROFILE_UPDATE_SELF_MSG(null, true), + DEVICE_UPDATE_SELF_MSG(null, true), + + DEDUPLICATION_TIMEOUT_SELF_MSG(null, true), + + DELAY_TIMEOUT_SELF_MSG(null, true), + + MSG_COUNT_SELF_MSG(null, true); + + public static final List NODE_CONNECTIONS = EnumSet.allOf(TbMsgType.class).stream() - .map(TbMsgType::getRuleNodeConnection).filter(Objects::nonNull).collect(Collectors.toUnmodifiableList()); + .filter(tbMsgType -> !tbMsgType.isTellSelfOnly()) + .map(TbMsgType::getRuleNodeConnection) + .filter(Objects::nonNull) + .collect(Collectors.toUnmodifiableList()); @Getter private final String ruleNodeConnection; + @Getter + private final boolean tellSelfOnly; + + TbMsgType(String ruleNodeConnection, boolean tellSelfOnly) { + this.ruleNodeConnection = ruleNodeConnection; + this.tellSelfOnly = tellSelfOnly; + } + TbMsgType(String ruleNodeConnection) { this.ruleNodeConnection = ruleNodeConnection; + this.tellSelfOnly = false; } public static String getRuleNodeConnectionOrElseOther(String msgType) { 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 c1f9dffd17..58f2089aed 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,10 +22,18 @@ 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.DEDUPLICATION_TIMEOUT_SELF_MSG; +import static org.thingsboard.server.common.data.msg.TbMsgType.DELAY_TIMEOUT_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; +import static org.thingsboard.server.common.data.msg.TbMsgType.MSG_COUNT_SELF_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_FAILURE; import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_SUCCESS; +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_UPDATE_SELF_MSG; +import static org.thingsboard.server.common.data.msg.TbMsgType.GENERATOR_NODE_SELF_MSG; +import static org.thingsboard.server.common.data.msg.TbMsgType.SEND_EMAIL; class TbMsgTypeTest { @@ -35,7 +43,15 @@ class TbMsgTypeTest { ENTITY_ASSIGNED_TO_EDGE, ENTITY_UNASSIGNED_FROM_EDGE, PROVISION_FAILURE, - PROVISION_SUCCESS + PROVISION_SUCCESS, + SEND_EMAIL, + GENERATOR_NODE_SELF_MSG, + DEVICE_PROFILE_PERIODIC_SELF_MSG, + DEVICE_PROFILE_UPDATE_SELF_MSG, + DEVICE_UPDATE_SELF_MSG, + DEDUPLICATION_TIMEOUT_SELF_MSG, + DELAY_TIMEOUT_SELF_MSG, + MSG_COUNT_SELF_MSG ); 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 9848046b11..c6c1a36694 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 @@ -29,10 +29,12 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.gen.MsgProtos; import org.thingsboard.server.common.msg.queue.TbMsgCallback; import java.io.Serializable; +import java.util.Objects; import java.util.UUID; /** @@ -42,7 +44,9 @@ import java.util.UUID; @Slf4j public final class TbMsg implements Serializable { - public static final String EMPTY = "{}"; + public static final String EMPTY_JSON_OBJECT = "{}"; + public static final String EMPTY_JSON_ARRAY = "[]"; + public static final String EMPTY_STRING = ""; private final String queueName; private final UUID id; @@ -68,61 +72,208 @@ public final class TbMsg implements Serializable { return ctx.getAndIncrementRuleNodeCounter(); } + @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { return newMsg(queueName, type, originator, null, metaData, data, ruleChainId, ruleNodeId); } + /** + * Creates a new TbMsg instance with the specified parameters. + * + *

Deprecated: This method is deprecated since version 3.5.2 and should only be used when you need to + * specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types, + * it is recommended to use the {@link #newMsg(String, TbMsgType, EntityId, CustomerId, TbMsgMetaData, String, RuleChainId, RuleNodeId)} + * method instead.

+ * + * @param queueName the name of the queue where the message will be sent + * @param type the type of the message + * @param originator the originator of the message + * @param customerId the ID of the customer associated with the message + * @param metaData the metadata of the message + * @param data the data of the message + * @param ruleChainId the ID of the rule chain associated with the message + * @param ruleNodeId the ID of the rule node associated with the message + * @return new TbMsg instance + */ + @Deprecated(since = "3.5.2") public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); } + @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) { return newMsg(type, originator, null, metaData, data); } + @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); } + public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { + return newMsg(queueName, type, originator, null, metaData, data, ruleChainId, ruleNodeId); + } + + public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { + return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, customerId, + metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); + } + + public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { + return newMsg(type, originator, null, metaData, data); + } + + public static TbMsg newMsg(TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, customerId, + metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); + } + // REALLY NEW MSG + /** + * Creates a new TbMsg instance with the specified parameters. + * + *

Deprecated: This method is deprecated since version 3.5.2 and should only be used when you need to + * specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types, + * it is recommended to use the {@link #newMsg(String, TbMsgType, EntityId, TbMsgMetaData, String)} + * method instead.

+ * + * @param queueName the name of the queue where the message will be sent + * @param type the type of the message + * @param originator the originator of the message + * @param metaData the metadata of the message + * @param data the data of the message + * @return new TbMsg instance + */ + @Deprecated(since = "3.5.2") public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data) { return newMsg(queueName, type, originator, null, metaData, data); } + /** + * Creates a new TbMsg instance with the specified parameters. + * + *

Deprecated: This method is deprecated since version 3.5.2 and should only be used when you need to + * specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types, + * it is recommended to use the {@link #newMsg(String, TbMsgType, EntityId, CustomerId, TbMsgMetaData, String)} + * method instead.

+ * + * @param queueName the name of the queue where the message will be sent + * @param type the type of the message + * @param originator the originator of the message + * @param customerId the ID of the customer associated with the message + * @param metaData the metadata of the message + * @param data the data of the message + * @return new TbMsg instance + */ + @Deprecated(since = "3.5.2") public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); } + @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), dataType, data, null, null, null, TbMsgCallback.EMPTY); } + /** + * Creates a new TbMsg instance with the specified parameters. + * + *

Deprecated: This method is deprecated since version 3.5.2 and should only be used when you need to + * specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types, + * it is recommended to use the {@link #newMsg(TbMsgType, EntityId, TbMsgMetaData, TbMsgDataType, String)} + * method instead.

+ * + * @param type the type of the message + * @param originator the originator of the message + * @param metaData the metadata of the message + * @param dataType the dataType of the message + * @param data the data of the message + * @return new TbMsg instance + */ + @Deprecated(since = "3.5.2") public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { return newMsg(type, originator, null, metaData, dataType, data); } + public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { + return newMsg(queueName, type, originator, null, metaData, data); + } + + public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { + return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, customerId, + metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); + } + + public static TbMsg newMsg(TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, customerId, + metaData.copy(), dataType, data, null, null, null, TbMsgCallback.EMPTY); + } + + public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { + return newMsg(type, originator, null, metaData, dataType, data); + } + // For Tests only + @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), dataType, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); } + @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) { return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, callback); } + /** + * Transforms an existing TbMsg instance by changing its message type, originator, metadata, and data. + * + *

Deprecated: This method is deprecated since version 3.5.2 and should only be used when you need to + * specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types, + * it is recommended to use the {@link #transformMsg(TbMsg, TbMsgType, EntityId, TbMsgMetaData, String)} + * method instead.

+ * + * + * @param tbMsg the TbMsg instance to transform + * @param type the new message type + * @param originator the new originator + * @param metaData the new metadata + * @param data the new data + * @return the transformed TbMsg instance + */ + @Deprecated(since = "3.5.2") public static TbMsg transformMsg(TbMsg tbMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, type, originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType, data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.callback); } + public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, null, + metaData.copy(), dataType, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); + } + + public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) { + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, null, + metaData.copy(), TbMsgDataType.JSON, data, null, null, null, callback); + } + + public static TbMsg transformMsg(TbMsg tbMsg, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, type.name(), originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType, + data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.callback); + } + + public static TbMsg transformMsgOriginator(TbMsg tbMsg, EntityId originatorId) { + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, originatorId, tbMsg.getCustomerId(), tbMsg.metaData, tbMsg.dataType, + tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); + } + public static TbMsg transformMsgData(TbMsg tbMsg, String data) { return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); @@ -133,6 +284,11 @@ public final class TbMsg implements Serializable { tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } + public static TbMsg transformMsg(TbMsg tbMsg, TbMsgMetaData metadata, String data) { + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, metadata, tbMsg.dataType, + data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); + } + public static TbMsg transformMsg(TbMsg tbMsg, CustomerId customerId) { return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); @@ -185,11 +341,7 @@ public final class TbMsg implements Serializable { this.ruleChainId = ruleChainId; this.ruleNodeId = ruleNodeId; this.ctx = ctx != null ? ctx : new TbMsgProcessingCtx(); - if (callback != null) { - this.callback = callback; - } else { - this.callback = TbMsgCallback.EMPTY; - } + this.callback = Objects.requireNonNullElse(callback, TbMsgCallback.EMPTY); } public static ByteString toByteString(TbMsg msg) { 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 6500467af7..497540ee44 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 @@ -1151,7 +1151,7 @@ public class DefaultTransportService implements TransportService { queueName = deviceProfile.getDefaultQueueName(); } - TbMsg tbMsg = TbMsg.newMsg(queueName, tbMsgType.name(), deviceId, customerId, metaData, gson.toJson(json), ruleChainId, null); + TbMsg tbMsg = TbMsg.newMsg(queueName, tbMsgType, deviceId, customerId, metaData, gson.toJson(json), ruleChainId, null); sendToRuleEngine(tenantId, tbMsg, 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 88bf80e9f9..be35e74321 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 @@ -36,6 +36,7 @@ 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.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleNode; @@ -183,12 +184,41 @@ public interface TbContext { void ack(TbMsg tbMsg); + @Deprecated(since = "3.5.2", forRemoval = true) TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data); + /** + * Creates a new TbMsg instance with the specified parameters. + * + *

Deprecated: This method is deprecated since version 3.5.2 and should only be used when you need to + * specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types, + * it is recommended to use the {@link #newMsg(String, TbMsgType, EntityId, CustomerId, TbMsgMetaData, String)} + * method instead.

+ * + * @param queueName the name of the queue where the message will be sent + * @param type the type of the message + * @param originator the originator of the message + * @param customerId the ID of the customer associated with the message + * @param metaData the metadata of the message + * @param data the data of the message + * @return new TbMsg instance + */ + @Deprecated(since = "3.5.2") TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data); + @Deprecated(since = "3.5.2", forRemoval = true) TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data); + TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data); + + TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data); + + TbMsg transformMsg(TbMsg origMsg, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data); + + TbMsg transformMsg(TbMsg origMsg, TbMsgMetaData metaData, String data); + + TbMsg transformMsgOriginator(TbMsg origMsg, EntityId originator); + TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId); TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId); @@ -196,7 +226,7 @@ public interface TbContext { TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId); // TODO: Does this changes the message? - TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action); + TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, TbMsgType actionMsgType); TbMsg attributesUpdatedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List attributes); diff --git a/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java b/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java index 956cfaf40c..cb5514a82b 100644 --- a/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java +++ b/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java @@ -22,6 +22,7 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.JacksonUtil; 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; @@ -43,7 +44,7 @@ public class TbNodeUtilsTest { ObjectNode node = JacksonUtil.newObjectNode(); node.put("data_key", "data_value"); - TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); String result = TbNodeUtils.processPattern(pattern, msg); Assert.assertEquals("ABC metadata_value data_value", result); } @@ -57,7 +58,7 @@ public class TbNodeUtilsTest { ObjectNode node = JacksonUtil.newObjectNode(); node.put("key", "data_value"); - TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); String result = TbNodeUtils.processPattern(pattern, msg); Assert.assertEquals(pattern, result); } @@ -71,7 +72,7 @@ public class TbNodeUtilsTest { ObjectNode node = JacksonUtil.newObjectNode(); node.put("key", "data_value"); - TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); String result = TbNodeUtils.processPattern(pattern, msg); Assert.assertEquals("ABC metadata_value data_value", result); } @@ -92,7 +93,7 @@ public class TbNodeUtilsTest { ObjectNode node = JacksonUtil.newObjectNode(); node.set("key1", key1Node); - TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); String result = TbNodeUtils.processPattern(pattern, msg); Assert.assertEquals("ABC metadata_value value3", result); } @@ -113,7 +114,7 @@ public class TbNodeUtilsTest { ObjectNode node = JacksonUtil.newObjectNode(); node.set("key1", key1Node); - TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); String result = TbNodeUtils.processPattern(pattern, msg); Assert.assertEquals("ABC metadata_value $[key1.key2[0].key3]", result); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index bc27154ad8..18317481bd 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.DataConstants; @@ -32,10 +33,6 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import static org.thingsboard.common.util.DonAsynchron.withCallback; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_CLEAR; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UPDATED; @Slf4j @@ -62,11 +59,11 @@ public abstract class TbAbstractAlarmNode processAlarm(TbContext ctx, TbMsg msg); - protected ListenableFuture buildAlarmDetails(TbContext ctx, TbMsg msg, JsonNode previousDetails) { + protected ListenableFuture buildAlarmDetails(TbMsg msg, JsonNode previousDetails) { try { TbMsg dummyMsg = msg; if (previousDetails != null) { TbMsgMetaData metaData = msg.getMetaData().copy(); metaData.putValue(PREV_ALARM_DETAILS, JacksonUtil.toString(previousDetails)); - dummyMsg = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), metaData, msg.getData()); + dummyMsg = TbMsg.transformMsg(msg, metaData); } return scriptEngine.executeJsonAsync(dummyMsg); } catch (Exception e) { @@ -101,7 +98,7 @@ public abstract class TbAbstractAlarmNode ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), alarmAction), throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable)); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java index a8d7d4985f..d0215a441f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java @@ -71,7 +71,7 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode clearAlarm(TbContext ctx, TbMsg msg, Alarm alarm) { ctx.logJsEvalRequest(); - ListenableFuture asyncDetails = buildAlarmDetails(ctx, msg, alarm.getDetails()); + ListenableFuture asyncDetails = buildAlarmDetails(msg, alarm.getDetails()); return Futures.transform(asyncDetails, details -> { ctx.logJsEvalResponse(); AlarmApiCallResult result = ctx.getAlarmService().clearAlarm(ctx.getTenantId(), alarm.getId(), System.currentTimeMillis(), details); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java index 84e82b76e8..b08fd1a562 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java @@ -120,7 +120,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode future = createRelationIfAbsent(ctx, msg, entity, relationType); return Futures.transform(future, result -> { if (result && config.isChangeOriginatorToRelatedEntity()) { - TbMsg tbMsg = ctx.transformMsg(msg, msg.getType(), entity.getEntityId(), msg.getMetaData(), msg.getData()); + TbMsg tbMsg = ctx.transformMsgOriginator(msg, entity.getEntityId()); return new RelationContainer(tbMsg, result); } return new RelationContainer(msg, result); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index d9a99c3229..0fd99651a1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -24,6 +24,8 @@ 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.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; @@ -32,9 +34,6 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; -import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; - @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -48,8 +47,6 @@ import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCES ) public class TbMsgCountNode implements TbNode { - private static final String TB_MSG_COUNT_NODE_MSG = "TbMsgCountNodeMsg"; - private AtomicLong messagesProcessed = new AtomicLong(0); private final Gson gson = new Gson(); private UUID nextTickId; @@ -68,7 +65,7 @@ public class TbMsgCountNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.getType().equals(TB_MSG_COUNT_NODE_MSG) && msg.getId().equals(nextTickId)) { + if (msg.getType().equals(TbMsgType.MSG_COUNT_SELF_MSG.name()) && msg.getId().equals(nextTickId)) { JsonObject telemetryJson = new JsonObject(); telemetryJson.addProperty(this.telemetryPrefix + "_" + ctx.getServiceId(), messagesProcessed.longValue()); @@ -77,8 +74,8 @@ public class TbMsgCountNode implements TbNode { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("delta", Long.toString(System.currentTimeMillis() - lastScheduledTs + delay)); - TbMsg tbMsg = TbMsg.newMsg(msg.getQueueName(), POST_TELEMETRY_REQUEST.name(), ctx.getTenantId(), msg.getCustomerId(), metaData, gson.toJson(telemetryJson)); - ctx.enqueueForTellNext(tbMsg, SUCCESS); + TbMsg tbMsg = TbMsg.newMsg(msg.getQueueName(), TbMsgType.POST_TELEMETRY_REQUEST, ctx.getTenantId(), msg.getCustomerId(), metaData, gson.toJson(telemetryJson)); + ctx.enqueueForTellNext(tbMsg, TbNodeConnectionType.SUCCESS); scheduleTickMsg(ctx, tbMsg); } else { messagesProcessed.incrementAndGet(); @@ -93,7 +90,7 @@ public class TbMsgCountNode implements TbNode { } lastScheduledTs = lastScheduledTs + delay; long curDelay = Math.max(0L, (lastScheduledTs - curTs)); - TbMsg tickMsg = ctx.newMsg(null, TB_MSG_COUNT_NODE_MSG, ctx.getSelfId(), msg != null ? msg.getCustomerId() : null, new TbMsgMetaData(), ""); + TbMsg tickMsg = ctx.newMsg(null, TbMsgType.MSG_COUNT_SELF_MSG, ctx.getSelfId(), msg != null ? msg.getCustomerId() : null, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); nextTickId = tickMsg.getId(); ctx.tellSelf(tickMsg, curDelay); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java index 4c8a5b28e1..1c487b2352 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java @@ -101,13 +101,13 @@ public class TbSnsNode extends TbAbstractExternalNode { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(MESSAGE_ID, result.getMessageId()); metaData.putValue(REQUEST_ID, result.getSdkResponseMetadata().getRequestId()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java index f8ebc8e295..d5f3f842e9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java @@ -27,7 +27,6 @@ import com.google.common.util.concurrent.ListenableFuture; 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; @@ -88,15 +87,15 @@ public class TbSqsNode extends TbAbstractExternalNode { public void onMsg(TbContext ctx, TbMsg msg) { withCallback(publishMessageAsync(ctx, msg), m -> tellSuccess(ctx, m), - t -> tellFailure(ctx, processException(ctx, msg, t), t)); + t -> tellFailure(ctx, processException(msg, t), t)); ackIfNeeded(ctx, msg); } private ListenableFuture publishMessageAsync(TbContext ctx, TbMsg msg) { - return ctx.getExternalCallExecutor().executeAsync(() -> publishMessage(ctx, msg)); + return ctx.getExternalCallExecutor().executeAsync(() -> publishMessage(msg)); } - private TbMsg publishMessage(TbContext ctx, TbMsg msg) { + private TbMsg publishMessage(TbMsg msg) { String queueUrl = TbNodeUtils.processPattern(this.config.getQueueUrlPattern(), msg); SendMessageRequest sendMsgRequest = new SendMessageRequest(); sendMsgRequest.withQueueUrl(queueUrl); @@ -115,10 +114,10 @@ public class TbSqsNode extends TbAbstractExternalNode { sendMsgRequest.withMessageGroupId(msg.getOriginator().toString()); } SendMessageResult result = this.sqsClient.sendMessage(sendMsgRequest); - return processSendMessageResult(ctx, msg, result); + return processSendMessageResult(msg, result); } - private TbMsg processSendMessageResult(TbContext ctx, TbMsg origMsg, SendMessageResult result) { + private TbMsg processSendMessageResult(TbMsg origMsg, SendMessageResult result) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(MESSAGE_ID, result.getMessageId()); metaData.putValue(REQUEST_ID, result.getSdkResponseMetadata().getRequestId()); @@ -131,13 +130,13 @@ public class TbSqsNode extends TbAbstractExternalNode { if (!StringUtils.isEmpty(result.getSequenceNumber())) { metaData.putValue(SEQUENCE_NUMBER, result.getSequenceNumber()); } - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } - private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { + private TbMsg processException(TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 75b63c59e9..7c3d6424e5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -30,6 +30,8 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +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.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; @@ -41,7 +43,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static org.thingsboard.common.util.DonAsynchron.withCallback; -import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( @@ -58,8 +59,6 @@ import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCES public class TbMsgGeneratorNode implements TbNode { - private static final String TB_MSG_GENERATOR_NODE_MSG = "TbMsgGeneratorNodeMsg"; - private TbMsgGeneratorNodeConfiguration config; private ScriptEngine scriptEngine; private long delay; @@ -107,13 +106,13 @@ public class TbMsgGeneratorNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { log.trace("onMsg, config {}, msg {}", config, msg); - if (initialized.get() && msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) { + if (initialized.get() && msg.getType().equals(TbMsgType.GENERATOR_NODE_SELF_MSG.name()) && msg.getId().equals(nextTickId)) { TbStopWatch sw = TbStopWatch.create(); withCallback(generate(ctx, msg), m -> { log.trace("onMsg onSuccess callback, took {}ms, config {}, msg {}", sw.stopAndGetTotalTimeMillis(), config, msg); if (initialized.get() && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { - ctx.enqueueForTellNext(m, SUCCESS); + ctx.enqueueForTellNext(m, TbNodeConnectionType.SUCCESS); scheduleTickMsg(ctx); currentMsgCount++; } @@ -137,7 +136,7 @@ public class TbMsgGeneratorNode implements TbNode { } lastScheduledTs = lastScheduledTs + delay; long curDelay = Math.max(0L, (lastScheduledTs - curTs)); - TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), ""); + TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TbMsgType.GENERATOR_NODE_SELF_MSG, ctx.getSelfId(), new TbMsgMetaData(), TbMsg.EMPTY_STRING); nextTickId = tickMsg.getId(); ctx.tellSelf(tickMsg, curDelay); } @@ -145,7 +144,7 @@ public class TbMsgGeneratorNode implements TbNode { private ListenableFuture generate(TbContext ctx, TbMsg msg) { log.trace("generate, config {}", config); if (prevMsg == null) { - prevMsg = ctx.newMsg(config.getQueueName(), "", originatorId, msg.getCustomerId(), new TbMsgMetaData(), "{}"); + prevMsg = ctx.newMsg(config.getQueueName(), "", originatorId, msg.getCustomerId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); } if (initialized.get()) { ctx.logJsEvalRequest(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index 5654b4095a..fae40ff5f5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -24,6 +24,7 @@ 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.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; @@ -60,10 +61,7 @@ import java.util.concurrent.TimeUnit; @Slf4j public class TbMsgDeduplicationNode implements TbNode { - private static final String TB_MSG_DEDUPLICATION_TIMEOUT_MSG = "TbMsgDeduplicationNodeMsg"; public static final int TB_MSG_DEDUPLICATION_RETRY_DELAY = 10; - private static final String EMPTY_DATA = ""; - private static final TbMsgMetaData EMPTY_META_DATA = new TbMsgMetaData(); private TbMsgDeduplicationNodeConfiguration config; @@ -82,7 +80,7 @@ public class TbMsgDeduplicationNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - if (TB_MSG_DEDUPLICATION_TIMEOUT_MSG.equals(msg.getType())) { + if (TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG.name().equals(msg.getType())) { processDeduplication(ctx, msg.getOriginator()); } else { processOnRegularMsg(ctx, msg); @@ -210,7 +208,7 @@ public class TbMsgDeduplicationNode implements TbNode { } private void scheduleTickMsg(TbContext ctx, EntityId deduplicationId) { - ctx.tellSelf(ctx.newMsg(null, TB_MSG_DEDUPLICATION_TIMEOUT_MSG, deduplicationId, EMPTY_META_DATA, EMPTY_DATA), deduplicationInterval + 1); + ctx.tellSelf(ctx.newMsg(null, TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG, deduplicationId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING), deduplicationInterval + 1); } private String getMergedData(List msgs) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index 00ba3acc50..dabba5970a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -23,6 +23,8 @@ 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.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; @@ -32,8 +34,6 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; -import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; - @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -50,8 +50,6 @@ import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCES ) public class TbMsgDelayNode implements TbNode { - private static final String TB_MSG_DELAY_NODE_MSG = "TbMsgDelayNodeMsg"; - private TbMsgDelayNodeConfiguration config; private Map pendingMsgs; @@ -63,7 +61,7 @@ public class TbMsgDelayNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.getType().equals(TB_MSG_DELAY_NODE_MSG)) { + if (msg.getType().equals(TbMsgType.DELAY_TIMEOUT_SELF_MSG.name())) { TbMsg pendingMsg = pendingMsgs.remove(UUID.fromString(msg.getData())); if (pendingMsg != null) { ctx.enqueueForTellNext( @@ -75,13 +73,13 @@ public class TbMsgDelayNode implements TbNode { pendingMsg.getMetaData(), pendingMsg.getData() ), - SUCCESS + TbNodeConnectionType.SUCCESS ); } } else { if (pendingMsgs.size() < config.getMaxPendingMsgs()) { pendingMsgs.put(msg.getId(), msg); - TbMsg tickMsg = ctx.newMsg(null, TB_MSG_DELAY_NODE_MSG, ctx.getSelfId(), msg.getCustomerId(), new TbMsgMetaData(), msg.getId().toString()); + TbMsg tickMsg = ctx.newMsg(null, TbMsgType.DELAY_TIMEOUT_SELF_MSG, ctx.getSelfId(), msg.getCustomerId(), TbMsgMetaData.EMPTY, msg.getId().toString()); ctx.tellSelf(tickMsg, getDelay(msg)); ctx.ack(msg); } else { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java index 3b927f05a6..b3f1c87171 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java @@ -28,7 +28,6 @@ import com.google.pubsub.v1.PubsubMessage; 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; @@ -103,28 +102,28 @@ public class TbPubSubNode extends TbAbstractExternalNode { ApiFuture messageIdFuture = this.pubSubClient.publish(pubsubMessageBuilder.build()); ApiFutures.addCallback(messageIdFuture, new ApiFutureCallback() { public void onSuccess(String messageId) { - TbMsg next = processPublishResult(ctx, msg, messageId); + TbMsg next = processPublishResult(msg, messageId); tellSuccess(ctx, next); } public void onFailure(Throwable t) { - TbMsg next = processException(ctx, msg, t); + TbMsg next = processException(msg, t); tellFailure(ctx, next, t); } }, ctx.getExternalCallExecutor()); } - private TbMsg processPublishResult(TbContext ctx, TbMsg origMsg, String messageId) { + private TbMsg processPublishResult(TbMsg origMsg, String messageId) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(MESSAGE_ID, messageId); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } - private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { + private TbMsg processException(TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } private Publisher initPubSubClient() throws IOException { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 80baa86505..a336fc1f77 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -165,24 +165,24 @@ public class TbKafkaNode extends TbAbstractExternalNode { private void processRecord(TbContext ctx, TbMsg msg, RecordMetadata metadata, Exception e) { if (e == null) { - tellSuccess(ctx, processResponse(ctx, msg, metadata)); + tellSuccess(ctx, processResponse(msg, metadata)); } else { - tellFailure(ctx, processException(ctx, msg, e), e); + tellFailure(ctx, processException(msg, e), e); } } - private TbMsg processResponse(TbContext ctx, TbMsg origMsg, RecordMetadata recordMetadata) { + private TbMsg processResponse(TbMsg origMsg, RecordMetadata recordMetadata) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(OFFSET, String.valueOf(recordMetadata.offset())); metaData.putValue(PARTITION, String.valueOf(recordMetadata.partition())); metaData.putValue(TOPIC, recordMetadata.topic()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } - private TbMsg processException(TbContext ctx, TbMsg origMsg, Exception e) { + private TbMsg processException(TbMsg origMsg, Exception e) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index ae661e48e4..a52641bedc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbEmail; @@ -27,6 +26,9 @@ 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.StringUtils; +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; @@ -34,9 +36,6 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; -import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; - @Slf4j @RuleNode( type = ComponentType.TRANSFORMATION, @@ -68,7 +67,7 @@ public class TbMsgToEmailNode implements TbNode { try { TbEmail email = convert(msg); TbMsg emailMsg = buildEmailMsg(ctx, msg, email); - ctx.tellNext(emailMsg, SUCCESS); + ctx.tellNext(emailMsg, TbNodeConnectionType.SUCCESS); } catch (Exception ex) { log.warn("Can not convert message to email " + ex.getMessage()); ctx.tellFailure(msg, ex); @@ -77,7 +76,7 @@ public class TbMsgToEmailNode implements TbNode { private TbMsg buildEmailMsg(TbContext ctx, TbMsg msg, TbEmail email) throws JsonProcessingException { String emailJson = JacksonUtil.toString(email); - return ctx.transformMsg(msg, SEND_EMAIL_TYPE, msg.getOriginator(), msg.getMetaData().copy(), emailJson); + return ctx.transformMsg(msg, TbMsgType.SEND_EMAIL, msg.getOriginator(), msg.getMetaData().copy(), emailJson); } private TbEmail convert(TbMsg msg) throws IOException { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java index eec403cdb9..66b23a29af 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java @@ -16,9 +16,8 @@ package org.thingsboard.rule.engine.mail; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.StringUtils; import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbEmail; @@ -26,6 +25,8 @@ 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.rule.engine.external.TbAbstractExternalNode; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -50,7 +51,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; public class TbSendEmailNode extends TbAbstractExternalNode { private static final String MAIL_PROP = "mail."; - static final String SEND_EMAIL_TYPE = "SEND_EMAIL"; private TbSendEmailNodeConfiguration config; private JavaMailSenderImpl mailSender; @@ -101,7 +101,7 @@ public class TbSendEmailNode extends TbAbstractExternalNode { } private void validateType(String type) { - if (!SEND_EMAIL_TYPE.equals(type)) { + if (!TbMsgType.SEND_EMAIL.name().equals(type)) { log.warn("Not expected msg type [{}] for SendEmail Node", type); throw new IllegalStateException("Not expected msg type " + type + " for SendEmail Node"); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index d4bdd320d1..25b9205602 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -115,7 +115,7 @@ public class CalculateDeltaNode implements TbNode { long period = previousData != null ? currentTs - previousData.ts : 0; result.put(config.getPeriodValueKey(), period); } - ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(result))); + ctx.tellSuccess(TbMsg.transformMsgData(msg, JacksonUtil.toString(result))); }, t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index 569c76c2a2..4cf564fd7b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -43,10 +43,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL; -import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_FIRST; -import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.MAX_FETCH_SIZE; - /** * Created by mshvayka on 04.09.18. */ @@ -76,7 +72,7 @@ public class TbGetTelemetryNode implements TbNode { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbGetTelemetryNodeConfiguration.class); tsKeyNames = config.getLatestTsKeyNames(); - limit = config.getFetchMode().equals(FETCH_MODE_ALL) ? validateLimit(config.getLimit()) : 1; + limit = config.getFetchMode().equals(TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL) ? validateLimit(config.getLimit()) : 1; fetchMode = config.getFetchMode(); orderByFetchAll = config.getOrderBy(); if (StringUtils.isEmpty(orderByFetchAll)) { @@ -86,7 +82,7 @@ public class TbGetTelemetryNode implements TbNode { } Aggregation parseAggregationConfig(String aggName) { - if (StringUtils.isEmpty(aggName) || !fetchMode.equals(FETCH_MODE_ALL)) { + if (StringUtils.isEmpty(aggName) || !fetchMode.equals(TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL)) { return Aggregation.NONE; } return Aggregation.valueOf(aggName); @@ -103,7 +99,7 @@ public class TbGetTelemetryNode implements TbNode { ListenableFuture> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(interval, keys)); DonAsynchron.withCallback(list, data -> { process(data, msg, keys); - ctx.tellSuccess(ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData())); + ctx.tellSuccess(msg); }, error -> ctx.tellFailure(msg, error), ctx.getDbCallbackExecutor()); } catch (Exception e) { ctx.tellFailure(msg, e); @@ -124,9 +120,9 @@ public class TbGetTelemetryNode implements TbNode { private String getOrderBy() { switch (fetchMode) { - case FETCH_MODE_ALL: + case TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL: return orderByFetchAll; - case FETCH_MODE_FIRST: + case TbGetTelemetryNodeConfiguration.FETCH_MODE_FIRST: return ASC_ORDER; default: return DESC_ORDER; @@ -135,7 +131,7 @@ public class TbGetTelemetryNode implements TbNode { private void process(List entries, TbMsg msg, List keys) { ObjectNode resultNode = JacksonUtil.newObjectNode(JacksonUtil.ALLOW_UNQUOTED_FIELD_NAMES_MAPPER); - if (FETCH_MODE_ALL.equals(fetchMode)) { + if (TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL.equals(fetchMode)) { entries.forEach(entry -> processArray(resultNode, entry)); } else { entries.forEach(entry -> processSingle(resultNode, entry)); @@ -216,7 +212,7 @@ public class TbGetTelemetryNode implements TbNode { if (limit != 0) { return limit; } else { - return MAX_FETCH_SIZE; + return TbGetTelemetryNodeConfiguration.MAX_FETCH_SIZE; } } 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 8fac7b1683..ccb7082e01 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 @@ -25,7 +25,6 @@ import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.mqtt.MqttConnectResult; 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; @@ -41,6 +40,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import javax.net.ssl.SSLException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -58,7 +58,7 @@ import java.util.concurrent.TimeoutException; ) public class TbMqttNode extends TbAbstractExternalNode { - private static final Charset UTF8 = Charset.forName("UTF-8"); + private static final Charset UTF8 = StandardCharsets.UTF_8; private static final String ERROR = "error"; @@ -85,17 +85,17 @@ public class TbMqttNode extends TbAbstractExternalNode { if (future.isSuccess()) { tellSuccess(ctx, msg); } else { - tellFailure(ctx, processException(ctx, msg, future.cause()), future.cause()); + tellFailure(ctx, processException(msg, future.cause()), future.cause()); } } ); ackIfNeeded(ctx, msg); } - private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { + private TbMsg processException(TbMsg origMsg, Throwable e) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 70f15935fc..9e2719bf53 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -189,7 +190,7 @@ class AlarmState { metaData.putValue(DataConstants.IS_CLEARED_ALARM, Boolean.TRUE.toString()); } setAlarmConditionMetadata(ruleState, metaData); - TbMsg newMsg = ctx.newMsg(lastMsgQueueName != null ? lastMsgQueueName : null, "ALARM", + TbMsg newMsg = ctx.newMsg(lastMsgQueueName != null ? lastMsgQueueName : null, TbMsgType.ALARM, originator, msg != null ? msg.getCustomerId() : null, metaData, data); ctx.enqueueForTellNext(newMsg, relationType); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index 4397200fe4..e368358299 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -224,7 +224,7 @@ class DeviceState { private boolean processAttributesDeleteNotification(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { boolean stateChanged = false; List keys = new ArrayList<>(); - new JsonParser().parse(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray().forEach(e -> keys.add(e.getAsString())); + JsonParser.parseString(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray().forEach(e -> keys.add(e.getAsString())); String scope = msg.getMetaData().getValue(DataConstants.SCOPE); if (StringUtils.isEmpty(scope)) { scope = DataConstants.CLIENT_SCOPE; @@ -252,7 +252,7 @@ class DeviceState { private boolean processAttributes(TbContext ctx, TbMsg msg, String scope) throws ExecutionException, InterruptedException { boolean stateChanged = false; - Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())); + Set attributes = JsonConverter.convertToAttributes(JsonParser.parseString(msg.getData())); if (!attributes.isEmpty()) { SnapshotUpdate update = merge(latestValues, attributes, scope); for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { @@ -267,7 +267,7 @@ class DeviceState { protected boolean processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { boolean stateChanged = false; - Map> tsKvMap = JsonConverter.convertToSortedTelemetry(new JsonParser().parse(msg.getData()), msg.getMetaDataTs()); + Map> tsKvMap = JsonConverter.convertToSortedTelemetry(JsonParser.parseString(msg.getData()), msg.getMetaDataTs()); // iterate over data by ts (ASC order). for (Map.Entry> entry : tsKvMap.entrySet()) { Long ts = entry.getKey(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index a8b3ef4f5f..0abb15f279 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.DeviceProfile; 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.msg.TbMsgType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -45,9 +46,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_DELETED; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UPDATED; - @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -62,9 +60,6 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UPDATED; configDirective = "tbDeviceProfileConfig" ) public class TbDeviceProfileNode implements TbNode { - private static final String PERIODIC_MSG_TYPE = "TbDeviceProfilePeriodicMsg"; - private static final String PROFILE_UPDATE_MSG_TYPE = "TbDeviceProfileUpdateMsg"; - private static final String DEVICE_UPDATE_MSG_TYPE = "TbDeviceUpdateMsg"; private TbDeviceProfileNodeConfiguration config; private RuleEngineDeviceProfileCache cache; @@ -109,12 +104,16 @@ public class TbDeviceProfileNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { EntityType originatorType = msg.getOriginator().getEntityType(); - if (msg.getType().equals(PERIODIC_MSG_TYPE)) { + if (msg.getType().equals(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG.name())) { scheduleAlarmHarvesting(ctx, msg); harvestAlarms(ctx, System.currentTimeMillis()); - } else if (msg.getType().equals(PROFILE_UPDATE_MSG_TYPE)) { + return; + } + if (msg.getType().equals(TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG.name())) { updateProfile(ctx, new DeviceProfileId(UUID.fromString(msg.getData()))); - } else if (msg.getType().equals(DEVICE_UPDATE_MSG_TYPE)) { + return; + } + if (msg.getType().equals(TbMsgType.DEVICE_UPDATE_SELF_MSG.name())) { JsonNode data = JacksonUtil.toJsonNode(msg.getData()); DeviceId deviceId = new DeviceId(UUID.fromString(data.get("deviceId").asText())); if (data.has("profileId")) { @@ -122,28 +121,28 @@ public class TbDeviceProfileNode implements TbNode { } else { removeDeviceState(deviceId); } - } else { - if (EntityType.DEVICE.equals(originatorType)) { - DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); - if (msg.getType().equals(ENTITY_UPDATED.name())) { - invalidateDeviceProfileCache(deviceId, msg.getData()); - ctx.tellSuccess(msg); - } else if (msg.getType().equals(ENTITY_DELETED.name())) { - removeDeviceState(deviceId); - ctx.tellSuccess(msg); + return; + } + if (EntityType.DEVICE.equals(originatorType)) { + DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); + if (msg.getType().equals(TbMsgType.ENTITY_UPDATED.name())) { + invalidateDeviceProfileCache(deviceId, msg.getData()); + ctx.tellSuccess(msg); + } else if (msg.getType().equals(TbMsgType.ENTITY_DELETED.name())) { + removeDeviceState(deviceId); + ctx.tellSuccess(msg); + } else { + DeviceState deviceState = getOrCreateDeviceState(ctx, deviceId, null); + if (deviceState != null) { + deviceState.process(ctx, msg); } else { - DeviceState deviceState = getOrCreateDeviceState(ctx, deviceId, null); - if (deviceState != null) { - deviceState.process(ctx, msg); - } else { - log.info("Device was not found! Most probably device [" + deviceId + "] has been removed from the database. Acknowledging msg."); - ctx.ack(msg); - } + log.info("Device was not found! Most probably device [" + deviceId + "] has been removed from the database. Acknowledging msg."); + ctx.ack(msg); } - } else { - ctx.tellSuccess(msg); } + return; } + ctx.tellSuccess(msg); } @Override @@ -171,7 +170,7 @@ public class TbDeviceProfileNode implements TbNode { } protected void scheduleAlarmHarvesting(TbContext ctx, TbMsg msg) { - TbMsg periodicCheck = TbMsg.newMsg(PERIODIC_MSG_TYPE, ctx.getTenantId(), msg != null ? msg.getCustomerId() : null, TbMsgMetaData.EMPTY, "{}"); + TbMsg periodicCheck = TbMsg.newMsg(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG, ctx.getTenantId(), msg != null ? msg.getCustomerId() : null, TbMsgMetaData.EMPTY, "{}"); ctx.tellSelf(periodicCheck, TimeUnit.MINUTES.toMillis(1)); } @@ -196,7 +195,7 @@ public class TbDeviceProfileNode implements TbNode { } protected void onProfileUpdate(DeviceProfile profile) { - ctx.tellSelf(TbMsg.newMsg(PROFILE_UPDATE_MSG_TYPE, ctx.getTenantId(), TbMsgMetaData.EMPTY, profile.getId().getId().toString()), 0L); + ctx.tellSelf(TbMsg.newMsg(TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG, ctx.getTenantId(), TbMsgMetaData.EMPTY, profile.getId().getId().toString()), 0L); } private void onDeviceUpdate(DeviceId deviceId, DeviceProfile deviceProfile) { @@ -205,7 +204,7 @@ public class TbDeviceProfileNode implements TbNode { if (deviceProfile != null) { msgData.put("deviceProfileId", deviceProfile.getId().getId().toString()); } - ctx.tellSelf(TbMsg.newMsg(DEVICE_UPDATE_MSG_TYPE, ctx.getTenantId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(msgData)), 0L); + ctx.tellSelf(TbMsg.newMsg(TbMsgType.DEVICE_UPDATE_SELF_MSG, ctx.getTenantId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(msgData)), 0L); } protected void invalidateDeviceProfileCache(DeviceId deviceId, String deviceJson) { @@ -218,7 +217,7 @@ public class TbDeviceProfileNode implements TbNode { removeDeviceState(deviceId); } } catch (IllegalArgumentException e) { - log.debug("[{}] Received device update notification with non-device msg body: [{}][{}]", ctx.getSelfId(), deviceId, e); + log.debug("[{}] Received device update notification with non-device msg body: [{}]", ctx.getSelfId(), deviceId, e); } } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java index 4ae634902f..b9cc8209a7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java @@ -24,7 +24,6 @@ import com.rabbitmq.client.MessageProperties; 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; @@ -86,7 +85,7 @@ public class TbRabbitMqNode extends TbAbstractExternalNode { public void onMsg(TbContext ctx, TbMsg msg) { withCallback(publishMessageAsync(ctx, msg), m -> tellSuccess(ctx, m), - t -> tellFailure(ctx, processException(ctx, msg, t), t)); + t -> tellFailure(ctx, processException(msg, t), t)); ackIfNeeded(ctx, msg); } @@ -115,10 +114,10 @@ public class TbRabbitMqNode extends TbAbstractExternalNode { return msg; } - private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { + private TbMsg processException(TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 81033191d6..70a22a692e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -203,7 +203,7 @@ public class TbHttpClient { future.addCallback(new ListenableFutureCallback<>() { @Override public void onFailure(Throwable throwable) { - onFailure.accept(processException(ctx, msg, throwable), throwable); + onFailure.accept(processException(msg, throwable), throwable); } @Override @@ -211,7 +211,7 @@ public class TbHttpClient { if (responseEntity.getStatusCode().is2xxSuccessful()) { onSuccess.accept(processResponse(ctx, msg, responseEntity)); } else { - onFailure.accept(processFailureResponse(ctx, msg, responseEntity), null); + onFailure.accept(processFailureResponse(msg, responseEntity), null); } } }); @@ -260,8 +260,8 @@ public class TbHttpClient { metaData.putValue(STATUS_CODE, response.getStatusCode().value() + ""); metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); headersToMetaData(response.getHeaders(), metaData::putValue); - String body = response.getBody() == null ? "{}" : response.getBody(); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, body); + String body = response.getBody() == null ? TbMsg.EMPTY_JSON_OBJECT : response.getBody(); + return ctx.transformMsg(origMsg, metaData, body); } void headersToMetaData(Map> headers, BiConsumer consumer) { @@ -279,17 +279,17 @@ public class TbHttpClient { }); } - private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { + private TbMsg processFailureResponse(TbMsg origMsg, ResponseEntity response) { TbMsgMetaData metaData = origMsg.getMetaData(); metaData.putValue(STATUS, response.getStatusCode().name()); metaData.putValue(STATUS_CODE, response.getStatusCode().value() + ""); metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); metaData.putValue(ERROR_BODY, response.getBody()); headersToMetaData(response.getHeaders(), metaData::putValue); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } - private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { + private TbMsg processException(TbMsg origMsg, Throwable e) { TbMsgMetaData metaData = origMsg.getMetaData(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); if (e instanceof RestClientResponseException) { @@ -298,7 +298,7 @@ public class TbHttpClient { metaData.putValue(STATUS_CODE, restClientResponseException.getRawStatusCode() + ""); metaData.putValue(ERROR_BODY, restClientResponseException.getResponseBodyAsString()); } - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } private HttpHeaders prepareHeaders(TbMsg msg) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java index 94b0e5d078..2a4df82715 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java @@ -18,7 +18,6 @@ package org.thingsboard.rule.engine.rest; 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; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java index 5f9a2f63ed..26d22b5ef2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -57,7 +57,6 @@ public class TbSendRPCRequestNode implements TbNode { private Random random = new Random(); private Gson gson = new Gson(); - private JsonParser jsonParser = new JsonParser(); private TbSendRpcRequestNodeConfiguration config; @Override @@ -67,7 +66,7 @@ public class TbSendRPCRequestNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - JsonObject json = jsonParser.parse(msg.getData()).getAsJsonObject(); + JsonObject json = JsonParser.parseString(msg.getData()).getAsJsonObject(); String tmp; if (msg.getOriginator().getEntityType() != EntityType.DEVICE) { ctx.tellFailure(msg, new RuntimeException("Message originator is not a device entity!")); @@ -117,7 +116,7 @@ public class TbSendRPCRequestNode implements TbNode { ctx.getRpcService().sendRpcRequestToDevice(request, ruleEngineDeviceRpcResponse -> { if (ruleEngineDeviceRpcResponse.getError().isEmpty()) { - TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), ruleEngineDeviceRpcResponse.getResponse().orElse("{}")); + TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), ruleEngineDeviceRpcResponse.getResponse().orElse(TbMsg.EMPTY_JSON_OBJECT)); ctx.enqueueForTellNext(next, TbNodeConnectionType.SUCCESS); } else { TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), wrap("error", ruleEngineDeviceRpcResponse.getError().get().name())); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index 5cc5dfe23f..4d1a092097 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.NoSuchElementException; @@ -74,14 +73,7 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode metaDataMap.remove(key)); + keysToDelete.forEach(metaDataMap::remove); metaData = new TbMsgMetaData(metaDataMap); } else { JsonNode dataNode = JacksonUtil.toJsonNode(msgData); @@ -94,7 +94,7 @@ public class TbDeleteKeysNode implements TbNode { if (keysToDelete.isEmpty()) { ctx.tellSuccess(msg); } else { - ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), metaData, msgData)); + ctx.tellSuccess(TbMsg.transformMsg(msg, metaData, msgData)); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java index da7d765a95..0e85a50c34 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java @@ -70,7 +70,7 @@ public class TbJsonPathNode implements TbNode { if (!TbJsonPathNodeConfiguration.DEFAULT_JSON_PATH.equals(this.jsonPathValue)) { try { Object jsonPathData = jsonPath.read(msg.getData(), this.configurationJsonPath); - ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(jsonPathData))); + ctx.tellSuccess(TbMsg.transformMsgData(msg, JacksonUtil.toString(jsonPathData))); } catch (PathNotFoundException e) { ctx.tellFailure(msg, e); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java index 3fce7494ea..88e6dc1a8b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java @@ -74,7 +74,7 @@ public class TbRenameKeysNode implements TbNode { } metaData = new TbMsgMetaData(metaDataMap); } else { - JsonNode dataNode = JacksonUtil.toJsonNode(msg.getData()); + JsonNode dataNode = JacksonUtil.toJsonNode(data); if (dataNode.isObject()) { ObjectNode msgData = (ObjectNode) dataNode; for (Map.Entry entry : renameKeysMapping.entrySet()) { @@ -89,7 +89,7 @@ public class TbRenameKeysNode implements TbNode { } } if (msgChanged) { - ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), metaData, data)); + ctx.tellSuccess(TbMsg.transformMsg(msg, metaData, data)); } else { ctx.tellSuccess(msg); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java index 1ee7ab7b03..8959d12c9f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java @@ -64,7 +64,7 @@ public class TbSplitArrayMsgNode implements TbNode { if (data.isEmpty()) { ctx.ack(msg); } else if (data.size() == 1) { - ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(data.get(0)))); + ctx.tellSuccess(TbMsg.transformMsgData(msg, JacksonUtil.toString(data.get(0)))); } else { TbMsgCallbackWrapper wrapper = new MultipleTbMsgsCallbackWrapper(data.size(), new TbMsgCallback() { @Override @@ -78,7 +78,7 @@ public class TbSplitArrayMsgNode implements TbNode { } }); data.forEach(msgNode -> { - TbMsg outMsg = TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(msgNode)); + TbMsg outMsg = TbMsg.transformMsgData(msg, JacksonUtil.toString(msgNode)); ctx.enqueueForTellNext(outMsg, TbNodeConnectionType.SUCCESS, wrapper::onSuccess, wrapper::onFailure); }); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 9173b7143c..33c6071769 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -34,10 +34,12 @@ import org.thingsboard.rule.engine.api.ScriptEngine; 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.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; @@ -45,6 +47,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.TbMsgType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -67,11 +70,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.DataConstants.IS_CLEARED_ALARM; -import static org.thingsboard.server.common.data.DataConstants.IS_EXISTING_ALARM; -import static org.thingsboard.server.common.data.DataConstants.IS_NEW_ALARM; -import static org.thingsboard.server.common.data.alarm.AlarmSeverity.CRITICAL; -import static org.thingsboard.server.common.data.alarm.AlarmSeverity.WARNING; @RunWith(MockitoJUnitRunner.class) public class TbAlarmNodeTest { @@ -108,10 +106,10 @@ public class TbAlarmNodeTest { } @Test - public void newAlarmCanBeCreated() throws ScriptException, IOException { + public void newAlarmCanBeCreated() { initWithCreateAlarmScript(); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long ts = msg.getTs(); when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); @@ -121,7 +119,7 @@ public class TbAlarmNodeTest { .endTs(ts) .tenantId(tenantId) .originator(originator) - .severity(CRITICAL) + .severity(AlarmSeverity.CRITICAL) .propagate(true) .type("SomeType") .details(null) @@ -139,16 +137,16 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Created")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -156,10 +154,10 @@ public class TbAlarmNodeTest { } @Test - public void buildDetailsThrowsException() throws ScriptException, IOException { + public void buildDetailsThrowsException() { initWithCreateAlarmScript(); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFailedFuture(new NotImplementedException("message"))); when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(null); @@ -179,10 +177,10 @@ public class TbAlarmNodeTest { } @Test - public void ifAlarmClearedCreateNew() throws ScriptException, IOException { + public void ifAlarmClearedCreateNew() { initWithCreateAlarmScript(); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long ts = msg.getTs(); Alarm clearedAlarm = Alarm.builder().cleared(true).acknowledged(true).build(); @@ -194,7 +192,7 @@ public class TbAlarmNodeTest { .endTs(ts) .tenantId(tenantId) .originator(originator) - .severity(CRITICAL) + .severity(AlarmSeverity.CRITICAL) .propagate(true) .type("SomeType") .details(null) @@ -213,16 +211,16 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Created")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); @@ -231,13 +229,13 @@ public class TbAlarmNodeTest { } @Test - public void alarmCanBeUpdated() throws IOException { + public void alarmCanBeUpdated() { initWithCreateAlarmScript(); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long oldEndDate = System.currentTimeMillis(); - Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(WARNING).endTs(oldEndDate).build(); + Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(AlarmSeverity.WARNING).endTs(oldEndDate).build(); when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(activeAlarm); @@ -245,7 +243,7 @@ public class TbAlarmNodeTest { Alarm expectedAlarm = Alarm.builder() .tenantId(tenantId) .originator(originator) - .severity(CRITICAL) + .severity(AlarmSeverity.CRITICAL) .propagate(true) .type("SomeType") .details(null) @@ -264,16 +262,16 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Updated")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_EXISTING_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_EXISTING_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -282,19 +280,19 @@ public class TbAlarmNodeTest { } @Test - public void alarmCanBeCleared() throws ScriptException, IOException { + public void alarmCanBeCleared() { initWithClearAlarmScript(); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long oldEndDate = System.currentTimeMillis(); - Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(WARNING).endTs(oldEndDate).build(); + Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(AlarmSeverity.WARNING).endTs(oldEndDate).build(); Alarm expectedAlarm = Alarm.builder() .tenantId(tenantId) .originator(originator) .cleared(true) - .severity(WARNING) + .severity(AlarmSeverity.WARNING) .propagate(false) .type("SomeType") .details(null) @@ -317,16 +315,16 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Cleared")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_CLEARED_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_CLEARED_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -337,18 +335,18 @@ public class TbAlarmNodeTest { public void alarmCanBeClearedWithAlarmOriginator() throws ScriptException, IOException { initWithClearAlarmScript(); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", alarmOriginator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, alarmOriginator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long oldEndDate = System.currentTimeMillis(); AlarmId id = new AlarmId(alarmOriginator.getId()); - Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(WARNING).endTs(oldEndDate).build(); + Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(AlarmSeverity.WARNING).endTs(oldEndDate).build(); activeAlarm.setId(id); Alarm expectedAlarm = Alarm.builder() .tenantId(tenantId) .originator(originator) .cleared(true) - .severity(WARNING) + .severity(AlarmSeverity.WARNING) .propagate(false) .type("SomeType") .details(null) @@ -372,16 +370,16 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Cleared")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(alarmOriginator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_CLEARED_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_CLEARED_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -410,14 +408,14 @@ public class TbAlarmNodeTest { String rawJson = "{\"alarmSeverity\": \"WARNING\", \"passed\": 5}"; metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long ts = msg.getTs(); Alarm expectedAlarm = Alarm.builder() .startTs(ts) .endTs(ts) .tenantId(tenantId) .originator(originator) - .severity(WARNING) + .severity(AlarmSeverity.WARNING) .propagate(true) .type("SomeType") .details(null) @@ -439,16 +437,16 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Created")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -476,14 +474,14 @@ public class TbAlarmNodeTest { node.init(ctx, nodeConfiguration); metaData.putValue("alarmSeverity", "WARNING"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long ts = msg.getTs(); Alarm expectedAlarm = Alarm.builder() .startTs(ts) .endTs(ts) .tenantId(tenantId) .originator(originator) - .severity(WARNING) + .severity(AlarmSeverity.WARNING) .propagate(true) .type("SomeType") .details(null) @@ -505,15 +503,15 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Created")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -525,7 +523,7 @@ public class TbAlarmNodeTest { for (int i = 0; i < 10; i++) { var config = new TbCreateAlarmNodeConfiguration(); config.setPropagateToTenant(true); - config.setSeverity(CRITICAL.name()); + config.setSeverity(AlarmSeverity.CRITICAL.name()); config.setAlarmType("SomeType" + i); config.setScriptLang(ScriptLanguage.JS); config.setAlarmDetailsBuildJs("DETAILS"); @@ -542,14 +540,14 @@ public class TbAlarmNodeTest { node.init(ctx, nodeConfiguration); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long ts = msg.getTs(); Alarm expectedAlarm = Alarm.builder() .startTs(ts) .endTs(ts) .tenantId(tenantId) .originator(originator) - .severity(CRITICAL) + .severity(AlarmSeverity.CRITICAL) .propagateToTenant(true) .type("SomeType" + i) .details(null) @@ -570,16 +568,16 @@ public class TbAlarmNodeTest { verify(ctx, atMost(10)).tellNext(any(), eq("Created")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx, atMost(10)).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -591,7 +589,7 @@ public class TbAlarmNodeTest { try { TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration(); config.setPropagate(true); - config.setSeverity(CRITICAL.name()); + config.setSeverity(AlarmSeverity.CRITICAL.name()); config.setAlarmType("SomeType"); config.setScriptLang(ScriptLanguage.JS); config.setAlarmDetailsBuildJs("DETAILS"); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java index 542ca75a51..7a9d95cad4 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java @@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.action; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,12 +29,14 @@ import org.thingsboard.rule.engine.TestDbCallbackExecutor; 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.EntityType; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.DeviceId; 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.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @@ -47,20 +48,16 @@ import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.relation.RelationService; import java.util.Collections; -import java.util.concurrent.Callable; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; @RunWith(MockitoJUnitRunner.class) public class TbCreateRelationNodeTest { - private static final String RELATION_TYPE_CONTAINS = "Contains"; - private TbCreateRelationNode node; @Mock @@ -98,11 +95,11 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); - when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) + when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(EntityRelation.CONTAINS_TYPE), eq(RelationTypeGroup.COMMON))) .thenReturn(Futures.immediateFuture(false)); - when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, RELATION_TYPE_CONTAINS, RelationTypeGroup.COMMON)))) + when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)))) .thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); @@ -125,15 +122,15 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); EntityRelation relation = new EntityRelation(); - when(ctx.getRelationService().findByToAndTypeAsync(any(), eq(msg.getOriginator()), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) + when(ctx.getRelationService().findByToAndTypeAsync(any(), eq(msg.getOriginator()), eq(EntityRelation.CONTAINS_TYPE), eq(RelationTypeGroup.COMMON))) .thenReturn(Futures.immediateFuture(Collections.singletonList(relation))); when(ctx.getRelationService().deleteRelationAsync(any(), eq(relation))).thenReturn(Futures.immediateFuture(true)); - when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) + when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(EntityRelation.CONTAINS_TYPE), eq(RelationTypeGroup.COMMON))) .thenReturn(Futures.immediateFuture(false)); - when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, RELATION_TYPE_CONTAINS, RelationTypeGroup.COMMON)))) + when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)))) .thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); @@ -156,20 +153,17 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); - when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) + when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(EntityRelation.CONTAINS_TYPE), eq(RelationTypeGroup.COMMON))) .thenReturn(Futures.immediateFuture(false)); - when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, RELATION_TYPE_CONTAINS, RelationTypeGroup.COMMON)))) + when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)))) .thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); - ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); - ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + verify(ctx).transformMsgOriginator(msgCaptor.capture(), originatorCaptor.capture()); assertEquals(assetId, originatorCaptor.getValue()); } @@ -188,9 +182,9 @@ public class TbCreateRelationNodeTest { private TbCreateRelationNodeConfiguration createRelationNodeConfig() { TbCreateRelationNodeConfiguration configuration = new TbCreateRelationNodeConfiguration(); configuration.setDirection(EntitySearchDirection.FROM.name()); - configuration.setRelationType(RELATION_TYPE_CONTAINS); + configuration.setRelationType(EntityRelation.CONTAINS_TYPE); configuration.setEntityCacheExpiration(300); - configuration.setEntityType("ASSET"); + configuration.setEntityType(EntityType.ASSET.name()); configuration.setEntityNamePattern("${name}"); configuration.setEntityTypePattern("${type}"); configuration.setCreateEntityIfNotExists(false); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java index f12288da3a..885002a694 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java @@ -24,6 +24,7 @@ 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.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -49,7 +50,7 @@ public class TbLogNodeTest { TbLogNode node = new TbLogNode(); String data = "{\"key\": \"value\"}"; TbMsgMetaData metaData = new TbMsgMetaData(Map.of("mdKey1", "mdValue1", "mdKey2", "23")); - TbMsg msg = TbMsg.newMsg("POST_TELEMETRY", TenantId.SYS_TENANT_ID, metaData, data); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, metaData, data); String logMessage = node.toLogMessage(msg); log.info(logMessage); @@ -65,7 +66,7 @@ public class TbLogNodeTest { void givenEmptyDataMsg_whenToLog_thenReturnString() { TbLogNode node = new TbLogNode(); TbMsgMetaData metaData = new TbMsgMetaData(Collections.emptyMap()); - TbMsg msg = TbMsg.newMsg("POST_TELEMETRY", TenantId.SYS_TENANT_ID, metaData, ""); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, metaData, ""); String logMessage = node.toLogMessage(msg); log.info(logMessage); @@ -81,7 +82,7 @@ public class TbLogNodeTest { void givenNullDataMsg_whenToLog_thenReturnString() { TbLogNode node = new TbLogNode(); TbMsgMetaData metaData = new TbMsgMetaData(Collections.emptyMap()); - TbMsg msg = TbMsg.newMsg("POST_TELEMETRY", TenantId.SYS_TENANT_ID, metaData, null); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, metaData, null); String logMessage = node.toLogMessage(msg); log.info(logMessage); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java index f44c58e663..0c51462429 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.msg.TbMsg; @@ -48,18 +49,12 @@ import java.util.UUID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; -import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @RunWith(MockitoJUnitRunner.class) public class TbMsgPushToEdgeNodeTest { - private static final List MISC_EVENTS = List.of(CONNECT_EVENT.name(), DISCONNECT_EVENT.name(), - ACTIVITY_EVENT.name(), INACTIVITY_EVENT.name()); + private static final List MISC_EVENTS = List.of(TbMsgType.CONNECT_EVENT, TbMsgType.DISCONNECT_EVENT, + TbMsgType.ACTIVITY_EVENT, TbMsgType.INACTIVITY_EVENT); TbMsgPushToEdgeNode node; @@ -89,8 +84,8 @@ public class TbMsgPushToEdgeNodeTest { Mockito.when(ctx.getEdgeService()).thenReturn(edgeService); Mockito.when(edgeService.findRelatedEdgeIdsByEntityId(tenantId, deviceId, new PageLink(TbMsgPushToEdgeNode.DEFAULT_PAGE_SIZE))).thenReturn(new PageData<>()); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), - TbMsgDataType.JSON, "{}", null, null); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, + TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, null, null); node.onMsg(ctx, msg); @@ -110,8 +105,8 @@ public class TbMsgPushToEdgeNodeTest { PageData edgePageData = new PageData<>(List.of(edgeId), 1, 1, false); Mockito.when(edgeService.findRelatedEdgeIdsByEntityId(tenantId, userId, new PageLink(TbMsgPushToEdgeNode.DEFAULT_PAGE_SIZE))).thenReturn(edgePageData); - TbMsg msg = TbMsg.newMsg(ATTRIBUTES_UPDATED.name(), userId, new TbMsgMetaData(), - TbMsgDataType.JSON, "{}", null, null); + TbMsg msg = TbMsg.newMsg(TbMsgType.ATTRIBUTES_UPDATED, userId, TbMsgMetaData.EMPTY, + TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, null, null); node.onMsg(ctx, msg); @@ -120,7 +115,7 @@ public class TbMsgPushToEdgeNodeTest { @Test public void testMiscEventsProcessedAsAttributesUpdated() { - for (String event : MISC_EVENTS) { + for (var event : MISC_EVENTS) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue(DataConstants.SCOPE, DataConstants.SERVER_SCOPE); testEvent(event, metaData, EdgeEventActionType.ATTRIBUTES_UPDATED, "kv"); @@ -129,12 +124,12 @@ public class TbMsgPushToEdgeNodeTest { @Test public void testMiscEventsProcessedAsTimeseriesUpdated() { - for (String event : MISC_EVENTS) { + for (var event : MISC_EVENTS) { testEvent(event, new TbMsgMetaData(), EdgeEventActionType.TIMESERIES_UPDATED, "data"); } } - private void testEvent(String event, TbMsgMetaData metaData, EdgeEventActionType expectedType, String dataKey) { + private void testEvent(TbMsgType event, TbMsgMetaData metaData, EdgeEventActionType expectedType, String dataKey) { Mockito.when(ctx.getTenantId()).thenReturn(tenantId); Mockito.when(ctx.getEdgeService()).thenReturn(edgeService); Mockito.when(ctx.getEdgeEventService()).thenReturn(edgeEventService); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java index 772d278ee8..785be659fd 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; 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.common.msg.queue.TbMsgCallback; @@ -46,7 +47,6 @@ import static org.mockito.Mockito.never; 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.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbAssetTypeSwitchNodeTest { @@ -118,7 +118,7 @@ class TbAssetTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java index 7bb8365895..a146b95ee1 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.AlarmId; 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.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -44,7 +45,6 @@ import static org.mockito.Mockito.never; 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.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbCheckAlarmStatusNodeTest { @@ -159,7 +159,7 @@ class TbCheckAlarmStatusNodeTest { } private TbMsg getTbMsg(String msgData) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, TbMsgMetaData.EMPTY, msgData); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, msgData); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java index 8926f36054..e69cc55d7e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java @@ -23,7 +23,9 @@ 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.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -38,15 +40,11 @@ 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.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE; -import static org.thingsboard.server.common.data.DataConstants.DEVICE_NAME; -import static org.thingsboard.server.common.data.DataConstants.DEVICE_TYPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbCheckMessageNodeTest { private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); - private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY); + private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); private TbCheckMessageNode node; @@ -193,12 +191,12 @@ class TbCheckMessageNodeTest { } private TbMsg getTbMsg(boolean emptyData) { - String data = emptyData ? TbMsg.EMPTY : "{\"temperature-0\": 25}"; + String data = emptyData ? TbMsg.EMPTY_JSON_OBJECT : "{\"temperature-0\": 25}"; var metadata = new TbMsgMetaData(); - metadata.putValue(DEVICE_NAME, "Test Device"); - metadata.putValue(DEVICE_TYPE, DEFAULT_DEVICE_TYPE); + metadata.putValue(DataConstants.DEVICE_NAME, "Test Device"); + metadata.putValue(DataConstants.DEVICE_TYPE, DataConstants.DEFAULT_DEVICE_TYPE); metadata.putValue("ts", String.valueOf(System.currentTimeMillis())); - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, metadata, data); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, DEVICE_ID, metadata, data); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java index 926d3b654b..bf7fa31f18 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.AssetId; 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.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @@ -54,14 +55,13 @@ import static org.mockito.Mockito.never; 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.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbCheckRelationNodeTest { private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); private static final DeviceId ORIGINATOR_ID = new DeviceId(UUID.randomUUID()); private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); - private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), ORIGINATOR_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY); + private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, ORIGINATOR_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); private TbCheckRelationNode node; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java index 3fe2e44f5d..a6f4a6e3cf 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; 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.common.msg.queue.TbMsgCallback; @@ -46,7 +47,6 @@ import static org.mockito.Mockito.never; 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.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbDeviceTypeSwitchNodeTest { @@ -118,6 +118,6 @@ class TbDeviceTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 2f75bbfe1b..6ce33fd009 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -27,10 +27,10 @@ import org.thingsboard.rule.engine.api.ScriptEngine; 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.msg.TbNodeConnectionType; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -59,7 +59,7 @@ public class TbJsFilterNodeTest { @Test public void falseEvaluationDoNotSendMsg() throws TbNodeException { initWithScript(); - TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, new TbMsgMetaData(), TbMsgDataType.JSON, TbMsg.EMPTY, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, new TbMsgMetaData(), TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(false)); node.onMsg(ctx, msg); @@ -71,7 +71,7 @@ public class TbJsFilterNodeTest { public void exceptionInJsThrowsException() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, TbMsg.EMPTY, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFailedFuture(new ScriptException("error"))); @@ -83,7 +83,7 @@ public class TbJsFilterNodeTest { public void metadataConditionCanBeTrue() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, TbMsg.EMPTY, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java index 763af2b1ee..3343c5fb92 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -58,7 +59,7 @@ public class TbJsSwitchNodeTest { metaData.putValue("humidity", "99"); String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); when(scriptEngine.executeSwitchAsync(msg)).thenReturn(Futures.immediateFuture(Sets.newHashSet("one", "three"))); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java index e79e2b77eb..4e2e5dc430 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java @@ -97,7 +97,7 @@ class TbMsgTypeFilterNodeTest { } private TbMsg getTbMsg(EntityId entityId, TbMsgType msgType) { - return TbMsg.newMsg(msgType.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY); + return TbMsg.newMsg(msgType, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java index cd4e21182f..c4fc8cd76d 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java @@ -88,7 +88,7 @@ class TbMsgTypeSwitchNodeTest { } private TbMsg getTbMsg(TbMsgType msgType) { - return TbMsg.newMsg(msgType.name(), DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY); + return TbMsg.newMsg(msgType, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java index 852552537f..3ed566b7e6 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.AssetId; 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.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -39,7 +40,6 @@ 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.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbOriginatorTypeFilterNodeTest { @@ -96,7 +96,7 @@ class TbOriginatorTypeFilterNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java index 64eb40fc41..796a7106fc 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -37,7 +38,6 @@ 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.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbOriginatorTypeSwitchNodeTest { @@ -90,7 +90,7 @@ class TbOriginatorTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java index 1a01dda8c2..64a239d57f 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -40,9 +41,6 @@ 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.thingsboard.rule.engine.geo.GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER; -import static org.thingsboard.rule.engine.geo.GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbGpsGeofencingFilterNodeTest { @@ -91,7 +89,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, - POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); // WHEN var exception = assertThrows(TbNodeException.class, () -> node.onMsg(ctx, msg)); @@ -109,7 +107,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, - POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); // WHEN var exception = assertThrows(TbNodeException.class, () -> node.onMsg(ctx, msg)); @@ -130,7 +128,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsgMetaData metadata = getMetadataForOldVersionPolygonPerimeter(); TbMsg msg = getTbMsg(deviceId, metadata, - POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); // WHEN node.onMsg(ctx, msg); @@ -154,7 +152,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsgMetaData metadata = getMetadataForOldVersionPolygonPerimeter(); TbMsg msg = getTbMsg(deviceId, metadata, - POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); // WHEN node.onMsg(ctx, msg); @@ -177,7 +175,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsgMetaData metadata = getMetadataForNewVersionPolygonPerimeter(); TbMsg msg = getTbMsg(deviceId, metadata, - POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); // WHEN node.onMsg(ctx, msg); @@ -200,7 +198,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsgMetaData metadata = getMetadataForNewVersionPolygonPerimeter(); TbMsg msg = getTbMsg(deviceId, metadata, - POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); // WHEN node.onMsg(ctx, msg); @@ -224,7 +222,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, - POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); // WHEN node.onMsg(ctx, msg); @@ -248,7 +246,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, - POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); // WHEN node.onMsg(ctx, msg); @@ -450,11 +448,11 @@ class TbGpsGeofencingFilterNodeTest { private TbMsg getTbMsg(EntityId entityId, TbMsgMetaData metadata, double latitude, double longitude) { String data = "{\"latitude\": " + latitude + ", \"longitude\": " + longitude + "}"; - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, metadata, data); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, metadata, data); } private TbMsg getEmptyArrayTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, "[]"); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, "[]"); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java index c95183717f..bf4d090c1c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.DeviceId; 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.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -56,26 +57,26 @@ public class TbMsgToEmailNodeTest { private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); @Test - public void msgCanBeConverted() throws IOException { + public void msgCanBeConverted() { initWithScript(); metaData.putValue("username", "oreo"); metaData.putValue("userEmail", "user@email.io"); metaData.putValue("name", "temp"); metaData.putValue("passed", "5"); metaData.putValue("count", "100"); - TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); emailNode.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("SEND_EMAIL", typeCaptor.getValue()); + assertEquals(TbMsgType.SEND_EMAIL, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("oreo", metadataCaptor.getValue().getValue("username")); assertNotSame(metaData, metadataCaptor.getValue()); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java index 2efc438f8f..3e19c7a736 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java @@ -45,6 +45,7 @@ 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.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.attributes.AttributesService; @@ -149,7 +150,7 @@ public class TbMathNodeTest { metaData.putValue("key2", "argumentA"); ObjectNode msgNode = JacksonUtil.newObjectNode() .put("key3", "argumentB").put("argumentA", 2).put("argumentB", 2); - TbMsg msg = TbMsg.newMsg("TEST", originator, metaData, msgNode.toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, msgNode.toString()); node.onMsg(ctx, msg); @@ -162,7 +163,7 @@ public class TbMathNodeTest { metaData.putValue("key2", "argumentC"); msgNode = JacksonUtil.newObjectNode() .put("key3", "argumentD").put("argumentC", 4).put("argumentD", 3); - msg = TbMsg.newMsg("TEST", originator, metaData, msgNode.toString()); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, msgNode.toString()); node.onMsg(ctx, msg); @@ -246,7 +247,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).put("b", arg2).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).put("b", arg2).toString()); node.onMsg(ctx, msg); @@ -269,7 +270,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).toString()); node.onMsg(ctx, msg); @@ -292,7 +293,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); node.onMsg(ctx, msg); @@ -315,7 +316,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); node.onMsg(ctx, msg); @@ -339,7 +340,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.TIME_SERIES, "b") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().toString()); Mockito.when(attributesService.find(tenantId, originator, DataConstants.SERVER_SCOPE, "a")) .thenReturn(Futures.immediateFuture(Optional.of(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("a", 2.0))))); @@ -367,7 +368,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); node.onMsg(ctx, msg); @@ -389,7 +390,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); node.onMsg(ctx, msg); @@ -411,7 +412,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); Mockito.when(telemetryService.saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble())) .thenReturn(Futures.immediateFuture(null)); @@ -437,7 +438,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) .thenReturn(Futures.immediateFuture(null)); @@ -462,7 +463,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) .thenReturn(Futures.immediateFuture(null)); @@ -493,7 +494,7 @@ public class TbMathNodeTest { new TbMathResult(TbMathArgumentType.MESSAGE_METADATA, "result", 3, false, false, null), tbMathArgument ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); node.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -513,7 +514,7 @@ public class TbMathNodeTest { new TbMathResult(TbMathArgumentType.TIME_SERIES, "result", 3, true, false, DataConstants.SERVER_SCOPE), new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); Throwable thrown = assertThrows(RuntimeException.class, () -> { node.onMsg(ctx, msg); }); @@ -527,7 +528,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), "[]"); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), "[]"); Throwable thrown = assertThrows(RuntimeException.class, () -> { node.onMsg(ctx, msg); }); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java index 6182d8cca5..9dbae201ac 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java @@ -40,6 +40,7 @@ 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.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -63,8 +64,6 @@ import static org.mockito.Mockito.reset; 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.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class CalculateDeltaNodeTest { @@ -104,7 +103,7 @@ public class CalculateDeltaNodeTest { public void givenInvalidMsgType_whenOnMsg_thenShouldTellNextOther() { // GIVEN var msgData = "{\"pulseCounter\": 42}"; - var msg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -119,7 +118,7 @@ public class CalculateDeltaNodeTest { public void givenInvalidMsgDataType_whenOnMsg_thenShouldTellNextOther() { // GIVEN var msgData = "[]"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -134,7 +133,7 @@ public class CalculateDeltaNodeTest { @Test public void givenInputKeyIsNotPresent_whenOnMsg_thenShouldTellNextOther() { // GIVEN - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); // WHEN node.onMsg(ctxMock, msg); @@ -158,7 +157,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", 40.5))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -188,7 +187,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 40L))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -218,7 +217,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("temperature", "40.0"))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -252,7 +251,7 @@ public class CalculateDeltaNodeTest { var msgData = "{\"temperature\": 42,\"airPressure\":123}"; var firstMsgMetaData = new TbMsgMetaData(); firstMsgMetaData.putValue("ts", String.valueOf(3L)); - var firstMsg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, firstMsgMetaData, msgData); + var firstMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, firstMsgMetaData, msgData); // WHEN node.onMsg(ctxMock, firstMsg); @@ -276,7 +275,7 @@ public class CalculateDeltaNodeTest { var secondMsgMetaData = new TbMsgMetaData(); secondMsgMetaData.putValue("ts", String.valueOf(6L)); - var secondMsg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, secondMsgMetaData, msgData); + var secondMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, secondMsgMetaData, msgData); // WHEN node.onMsg(ctxMock, secondMsg); @@ -307,7 +306,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", null))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -335,7 +334,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("pulseCounter", 200L))); var msgData = "{\"pulseCounter\":\"123\"}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -364,7 +363,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("pulseCounter", "high"))); var msgData = "{\"pulseCounter\":\"123\"}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN-THEN Assertions.assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) @@ -378,7 +377,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry("pulseCounter", false))); var msgData = "{\"pulseCounter\":true}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN-THEN Assertions.assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) @@ -392,7 +391,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new JsonDataEntry("pulseCounter", "{\"isActive\":false}"))); var msgData = "{\"pulseCounter\":{\"isActive\":true}}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN-THEN Assertions.assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java index dfd054edc7..5de25ebfc2 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java @@ -31,7 +31,9 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.CustomerId; 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.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -51,8 +53,6 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.common.data.security.DeviceCredentialsType.ACCESS_TOKEN; @ExtendWith(MockitoExtension.class) public class TbFetchDeviceCredentialsNodeTest { @@ -98,7 +98,7 @@ public class TbFetchDeviceCredentialsNodeTest { doReturn(deviceCredentialsServiceMock).when(ctxMock).getDeviceCredentialsService(); doAnswer(invocation -> { DeviceCredentials deviceCredentials = new DeviceCredentials(); - deviceCredentials.setCredentialsType(ACCESS_TOKEN); + deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); return deviceCredentials; }).when(deviceCredentialsServiceMock).findDeviceCredentialsByDeviceId(any(), any()); doAnswer(invocation -> JacksonUtil.newObjectNode()).when(deviceCredentialsServiceMock).toCredentialsInfo(any()); @@ -172,7 +172,7 @@ public class TbFetchDeviceCredentialsNodeTest { final var metaData = new TbMsgMetaData(mdMap); final String data = "{\"TestAttribute_1\": \"humidity\", \"TestAttribute_2\": \"voltage\"}"; - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, metaData, data, callbackMock); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, metaData, data, callbackMock); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java index cb36982b20..a889f91b77 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java @@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +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; @@ -240,7 +241,7 @@ public class TbGetAttributesNodeTest { public void givenFetchLatestTimeseriesToDataAndDataIsNotJsonObject_whenOnMsg_thenException() throws Exception { // GIVEN node = initNode(FetchTo.DATA, true, true); - var msg = TbMsg.newMsg("TEST", ORIGINATOR, new TbMsgMetaData(), "[]"); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -369,7 +370,7 @@ public class TbGetAttributesNodeTest { msgMetaData.putValue("client_attr_metadata", "client_attr_3"); msgMetaData.putValue("server_attr_metadata", "server_attr_3"); - return TbMsg.newMsg("TEST", entityId, msgMetaData, JacksonUtil.toString(msgData)); + return TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, entityId, msgMetaData, JacksonUtil.toString(msgData)); } private List getAttributeNames(String prefix) { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 12385f6e28..3a8f36f5dc 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -33,6 +33,7 @@ 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.Customer; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; @@ -47,6 +48,7 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +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; @@ -74,8 +76,6 @@ import static org.mockito.Mockito.never; 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; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetCustomerAttributeNodeTest { @@ -208,7 +208,7 @@ public class TbGetCustomerAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -223,7 +223,7 @@ public class TbGetCustomerAttributeNodeTest { // GIVEN var userId = new UserId(UUID.randomUUID()); - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), userId, new TbMsgMetaData(), "{}"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, userId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); when(ctxMock.getTenantId()).thenReturn(TENANT_ID); @@ -276,7 +276,7 @@ public class TbGetCustomerAttributeNodeTest { doReturn(device).when(deviceServiceMock).findDeviceById(eq(TENANT_ID), eq(device.getId())); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributesList)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -323,7 +323,7 @@ public class TbGetCustomerAttributeNodeTest { doReturn(Futures.immediateFuture(user)).when(userServiceMock).findUserByIdAsync(eq(TENANT_ID), eq(user.getId())); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributesList)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -467,7 +467,7 @@ public class TbGetCustomerAttributeNodeTest { var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index 540a260338..8b958bcc3c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -45,6 +45,7 @@ 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.id.UserId; +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; @@ -68,7 +69,6 @@ import static org.mockito.Mockito.never; 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.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetCustomerDetailsNodeTest { @@ -157,7 +157,7 @@ public class TbGetCustomerDetailsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -456,7 +456,7 @@ public class TbGetCustomerDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, msgMetaData, msgData); } private void mockFindCustomer() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index addd05bd0e..0057ec7c61 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.DashboardId; 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.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -51,7 +52,6 @@ import static org.mockito.Mockito.never; 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.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetOriginatorFieldsNodeTest { @@ -133,7 +133,7 @@ public class TbGetOriginatorFieldsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -162,7 +162,7 @@ public class TbGetOriginatorFieldsNodeTest { node.fetchTo = FetchTo.DATA; var msgMetaData = new TbMsgMetaData(); var msgData = "{\"temp\":42,\"humidity\":77}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -204,7 +204,7 @@ public class TbGetOriginatorFieldsNodeTest { node.fetchTo = FetchTo.DATA; var msgMetaData = new TbMsgMetaData(); var msgData = "{\"temp\":42,\"humidity\":77}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -247,7 +247,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -295,7 +295,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -353,7 +353,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), new DashboardId(UUID.randomUUID()), msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, new DashboardId(UUID.randomUUID()), msgMetaData, msgData); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index 1a638eca39..6b3a225eac 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -35,6 +35,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.data.RelationsQuery; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; @@ -53,6 +54,7 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; @@ -83,8 +85,6 @@ import static org.mockito.Mockito.never; 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; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetRelatedAttributeNodeTest { @@ -222,7 +222,7 @@ public class TbGetRelatedAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -290,7 +290,7 @@ public class TbGetRelatedAttributeNodeTest { doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(user.getId()), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(user.getId()), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributes)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -341,7 +341,7 @@ public class TbGetRelatedAttributeNodeTest { doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(secondCustomer.getId()), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(secondCustomer.getId()), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributes)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -591,7 +591,7 @@ public class TbGetRelatedAttributeNodeTest { msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; } - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index c163d68733..ffb053c93a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -31,6 +31,7 @@ import org.thingsboard.rule.engine.TestDbCallbackExecutor; 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.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; @@ -41,6 +42,7 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +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; @@ -61,8 +63,6 @@ import static org.mockito.Mockito.never; 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; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetTenantAttributeNodeTest { @@ -188,7 +188,7 @@ public class TbGetTenantAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -215,7 +215,7 @@ public class TbGetTenantAttributeNodeTest { when(ctxMock.getTenantId()).thenReturn(TENANT_ID); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributesList)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -256,7 +256,7 @@ public class TbGetTenantAttributeNodeTest { when(ctxMock.getTenantId()).thenReturn(TENANT_ID); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributesList)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -396,7 +396,7 @@ public class TbGetTenantAttributeNodeTest { var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java index 430a772269..2dda9ffaf0 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java @@ -32,6 +32,7 @@ import org.thingsboard.rule.engine.util.ContactBasedEntityDetails; import org.thingsboard.server.common.data.Tenant; 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.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -49,7 +50,6 @@ import static org.mockito.Mockito.never; 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.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetTenantDetailsNodeTest { @@ -127,7 +127,7 @@ public class TbGetTenantDetailsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -287,7 +287,7 @@ public class TbGetTenantDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); } private void mockFindTenant() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java index 0d94d9de16..6bc4a59610 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.FilterPredicateValue; @@ -62,10 +63,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_CLEAR; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; -import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class DeviceStateTest { @@ -94,9 +91,10 @@ public class DeviceStateTest { }); when(ctx.getAlarmService()).thenReturn(alarmService); - when(ctx.newMsg(any(), any(), any(), any(), any(), any())).thenAnswer(invocationOnMock -> { + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), any())).thenAnswer(invocationOnMock -> { + TbMsgType type = invocationOnMock.getArgument(1); String data = invocationOnMock.getArgument(invocationOnMock.getArguments().length - 1); - return TbMsg.newMsg(null, null, new TbMsgMetaData(), data); + return TbMsg.newMsg(type, null, TbMsgMetaData.EMPTY, data); }); } @@ -108,7 +106,7 @@ public class DeviceStateTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); DeviceState deviceState = createDeviceState(deviceId, alarmConfig); - TbMsg attributeUpdateMsg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), + TbMsg attributeUpdateMsg = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, deviceId, new TbMsgMetaData(), "{ \"enabled\": false }"); deviceState.process(ctx, attributeUpdateMsg); @@ -117,11 +115,11 @@ public class DeviceStateTest { verify(ctx).enqueueForTellNext(resultMsgCaptor.capture(), eq("Alarm Created")); Alarm alarm = JacksonUtil.fromString(resultMsgCaptor.getValue().getData(), Alarm.class); - deviceState.process(ctx, TbMsg.newMsg(ALARM_CLEAR.name(), deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); + deviceState.process(ctx, TbMsg.newMsg(TbMsgType.ALARM_CLEAR, deviceId, TbMsgMetaData.EMPTY, JacksonUtil.toString(alarm))); reset(ctx); String deletedAttributes = "{ \"attributes\": [ \"other\" ] }"; - deviceState.process(ctx, TbMsg.newMsg(ATTRIBUTES_DELETED.name(), deviceId, new TbMsgMetaData(), deletedAttributes)); + deviceState.process(ctx, TbMsg.newMsg(TbMsgType.ATTRIBUTES_DELETED, deviceId, new TbMsgMetaData(), deletedAttributes)); verify(ctx, never()).enqueueForTellNext(any(), anyString()); } @@ -131,17 +129,17 @@ public class DeviceStateTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); DeviceState deviceState = createDeviceState(deviceId, alarmConfig); - TbMsg attributeUpdateMsg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), - deviceId, new TbMsgMetaData(), "{ \"enabled\": false }"); + TbMsg attributeUpdateMsg = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, + deviceId, TbMsgMetaData.EMPTY, "{ \"enabled\": false }"); deviceState.process(ctx, attributeUpdateMsg); ArgumentCaptor resultMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx).enqueueForTellNext(resultMsgCaptor.capture(), eq("Alarm Created")); Alarm alarm = JacksonUtil.fromString(resultMsgCaptor.getValue().getData(), Alarm.class); - deviceState.process(ctx, TbMsg.newMsg(ALARM_CLEAR.name(), deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); + deviceState.process(ctx, TbMsg.newMsg(TbMsgType.ALARM_CLEAR, deviceId, TbMsgMetaData.EMPTY, JacksonUtil.toString(alarm))); - TbMsg alarmDeleteNotification = TbMsg.newMsg(ALARM_DELETE.name(), deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm)); + TbMsg alarmDeleteNotification = TbMsg.newMsg(TbMsgType.ALARM_DELETE, deviceId, TbMsgMetaData.EMPTY, JacksonUtil.toString(alarm)); assertDoesNotThrow(() -> { deviceState.process(ctx, alarmDeleteNotification); }); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index 20c529646c..03a9c17a60 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -54,6 +54,7 @@ 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.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValueSourceType; @@ -85,7 +86,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @RunWith(MockitoJUnitRunner.class) public class TbDeviceProfileNodeTest { @@ -122,8 +122,8 @@ public class TbDeviceProfileNodeTest { Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 42); - TbMsg msg = TbMsg.newMsg("123456789", deviceId, new TbMsgMetaData(), - TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); + TbMsg msg = TbMsg.newMsg("123456789", deviceId, TbMsgMetaData.EMPTY, + TbMsgDataType.JSON, JacksonUtil.toString(data)); node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); @@ -141,7 +141,7 @@ public class TbDeviceProfileNodeTest { Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 42); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); @@ -193,25 +193,25 @@ public class TbDeviceProfileNodeTest { Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")).thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())).thenReturn(theMsg); + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())).thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 42); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); - TbMsg theMsg2 = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "2"); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())).thenReturn(theMsg2); + TbMsg theMsg2 = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, "2"); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())).thenReturn(theMsg2); registerCreateAlarmMock(alarmService.updateAlarm(any()), false); - TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); verify(ctx).tellSuccess(msg2); @@ -286,13 +286,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(attrListListenableFuture); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + Mockito.when(ctx.newMsg(Mockito.any(), Mockito.any(TbMsgType.class), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 21); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -373,13 +373,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString())) .thenReturn(attrListListenableFuture); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 21); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -442,13 +442,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFutureWithLess); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -536,13 +536,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFuture); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -557,7 +557,7 @@ public class TbDeviceProfileNodeTest { Thread.sleep(halfOfAlarmDelay); - TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -660,13 +660,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listNoDurationAttribute); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -681,7 +681,7 @@ public class TbDeviceProfileNodeTest { Thread.sleep(halfOfAlarmDelay); - TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -769,13 +769,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFuture); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -784,7 +784,7 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); data.put("temperature", 151); - TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -885,13 +885,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listNoDurationAttribute); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -900,7 +900,7 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); data.put("temperature", 151); - TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -981,13 +981,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFuture); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1002,7 +1002,7 @@ public class TbDeviceProfileNodeTest { Thread.sleep(halfOfAlarmDelay); - TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -1079,13 +1079,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFuture); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1161,13 +1161,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFutureActiveSchedule); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); // Mockito.reset(ctx); @@ -1257,11 +1257,11 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFutureInactiveSchedule); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1335,13 +1335,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 25); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1408,13 +1408,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 40); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1491,13 +1491,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150L); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1576,13 +1576,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150L); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java index 7b6a54ae0b..2ddc20d5ed 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java @@ -32,6 +32,7 @@ import org.springframework.web.client.AsyncRestTemplate; import org.thingsboard.rule.engine.api.TbContext; 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.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -144,16 +145,15 @@ public class TbHttpClientTest { var httpClient = new TbHttpClient(config, eventLoop); httpClient.setHttpClient(asyncRestTemplate); - var msg = TbMsg.newMsg("GET", new DeviceId(EntityId.NULL_UUID), TbMsgMetaData.EMPTY, "{}"); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, new DeviceId(EntityId.NULL_UUID), TbMsgMetaData.EMPTY, "{}"); var successMsg = TbMsg.newMsg( - "SUCCESS", msg.getOriginator(), + TbMsgType.POST_TELEMETRY_REQUEST, msg.getOriginator(), msg.getMetaData(), msg.getData() ); var ctx = mock(TbContext.class); when(ctx.transformMsg( - eq(msg), eq(msg.getType()), - eq(msg.getOriginator()), + eq(msg), eq(msg.getMetaData()), eq(msg.getData()) )).thenReturn(successMsg); @@ -161,15 +161,14 @@ public class TbHttpClientTest { var capturedData = ArgumentCaptor.forClass(String.class); when(ctx.transformMsg( - eq(msg), eq(msg.getType()), - eq(msg.getOriginator()), + eq(msg), any(), capturedData.capture() )).thenReturn(successMsg); httpClient.processMessage(ctx, msg, m -> ctx.tellSuccess(msg), - (m, t) -> ctx.tellFailure(m, t)); + ctx::tellFailure); Awaitility.await() .atMost(30, TimeUnit.SECONDS) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java index 9185a31118..705f298513 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java @@ -16,7 +16,6 @@ package org.thingsboard.rule.engine.rest; import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; @@ -39,6 +38,7 @@ import org.thingsboard.server.common.data.id.DeviceId; 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.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -136,23 +136,18 @@ public class TbRestApiCallNodeTest { config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path)); initWithConfig(config); - TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); restNode.onMsg(ctx, msg); assertTrue("Server handled request", latch.await(10, TimeUnit.SECONDS)); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); - ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + verify(ctx).transformMsg(msgCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - - assertEquals("USER", typeCaptor.getValue()); - assertEquals(originator, originatorCaptor.getValue()); assertNotSame(metaData, metadataCaptor.getValue()); - assertEquals("{}", dataCaptor.getValue()); + assertEquals(TbMsg.EMPTY_JSON_OBJECT, dataCaptor.getValue()); } @Test @@ -202,22 +197,18 @@ public class TbRestApiCallNodeTest { config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path)); initWithConfig(config); - TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); restNode.onMsg(ctx, msg); assertTrue("Server handled request", latch.await(10, TimeUnit.SECONDS)); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); - ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + verify(ctx).transformMsg(msgCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("USER", typeCaptor.getValue()); - assertEquals(originator, originatorCaptor.getValue()); assertNotSame(metaData, metadataCaptor.getValue()); - assertEquals("{}", dataCaptor.getValue()); + assertEquals(TbMsg.EMPTY_JSON_OBJECT, dataCaptor.getValue()); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java index a72d343d10..7b12dbb2b1 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java @@ -80,7 +80,7 @@ public class TbSendRPCReplyNodeTest { Mockito.when(ctx.getRpcService()).thenReturn(rpcService); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, getDefaultMetadata(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, getDefaultMetadata(), TbMsgDataType.JSON, DUMMY_DATA, null, null); node.onMsg(ctx, msg); @@ -99,7 +99,7 @@ public class TbSendRPCReplyNodeTest { TbMsgMetaData defaultMetadata = getDefaultMetadata(); defaultMetadata.putValue(DataConstants.EDGE_ID, UUID.randomUUID().toString()); defaultMetadata.putValue(DataConstants.DEVICE_ID, UUID.randomUUID().toString()); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetadata, + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, defaultMetadata, TbMsgDataType.JSON, DUMMY_DATA, null, null); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java index 9b23a3aa31..632243676f 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java @@ -25,7 +25,9 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; 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.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -49,11 +51,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY; -import static org.thingsboard.server.common.data.DataConstants.SCOPE; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; @Slf4j public class TbMsgDeleteAttributesNodeTest { @@ -95,7 +92,7 @@ public class TbMsgDeleteAttributesNodeTest { @Test void givenDefaultConfig_whenVerify_thenOK() { TbMsgDeleteAttributesNodeConfiguration defaultConfig = new TbMsgDeleteAttributesNodeConfiguration().defaultConfiguration(); - assertThat(defaultConfig.getScope()).isEqualTo(SERVER_SCOPE); + assertThat(defaultConfig.getScope()).isEqualTo(DataConstants.SERVER_SCOPE); assertThat(defaultConfig.getKeys()).isEqualTo(Collections.emptyList()); } @@ -116,7 +113,7 @@ public class TbMsgDeleteAttributesNodeTest { void givenMsg_whenOnMsg_thenVerifyOutput_SendAttributesDeletedNotification_NotifyDevice() throws Exception { config.setSendAttributesDeletedNotification(true); config.setNotifyDevice(true); - config.setScope(SHARED_SCOPE); + config.setScope(DataConstants.SHARED_SCOPE); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); onMsg_thenVerifyOutput(true, true, false); @@ -136,12 +133,12 @@ public class TbMsgDeleteAttributesNodeTest { ); TbMsgMetaData metaData = new TbMsgMetaData(mdMap); if (notifyDeviceMetadata) { - metaData.putValue(NOTIFY_DEVICE_METADATA_KEY, "true"); - metaData.putValue(SCOPE, SHARED_SCOPE); + metaData.putValue(DataConstants.NOTIFY_DEVICE_METADATA_KEY, "true"); + metaData.putValue(DataConstants.SCOPE, DataConstants.SHARED_SCOPE); } final String data = "{\"TestAttribute_2\": \"humidity\", \"TestAttribute_3\": \"voltage\"}"; - TbMsg msg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), deviceId, metaData, data, callback); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, deviceId, metaData, data, callback); node.onMsg(ctx, msg); ArgumentCaptor successCaptor = ArgumentCaptor.forClass(Runnable.class); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java index 071ae9ce96..8ea7806ae2 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.id.CustomerId; 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.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -79,7 +80,7 @@ public class TbChangeOriginatorNodeTest { RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - TbMsg msg = TbMsg.newMsg( "ASSET", assetId, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, assetId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(ctx.getAssetService()).thenReturn(assetService); when(assetService.findAssetByIdAsync(any(),eq( assetId))).thenReturn(Futures.immediateFuture(asset)); @@ -87,11 +88,8 @@ public class TbChangeOriginatorNodeTest { node.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); - ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); - ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + verify(ctx).transformMsgOriginator(msgCaptor.capture(), originatorCaptor.capture()); assertEquals(customerId, originatorCaptor.getValue()); } @@ -107,18 +105,15 @@ public class TbChangeOriginatorNodeTest { RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - TbMsg msg = TbMsg.newMsg( "ASSET", assetId, new TbMsgMetaData(), TbMsgDataType.JSON,"{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, assetId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON,TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(ctx.getAssetService()).thenReturn(assetService); when(assetService.findAssetByIdAsync(any(), eq(assetId))).thenReturn(Futures.immediateFuture(asset)); node.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); - ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); - ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + verify(ctx).transformMsgOriginator(msgCaptor.capture(), originatorCaptor.capture()); assertEquals(customerId, originatorCaptor.getValue()); } @@ -134,7 +129,7 @@ public class TbChangeOriginatorNodeTest { RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - TbMsg msg = TbMsg.newMsg( "ASSET", assetId, new TbMsgMetaData(), TbMsgDataType.JSON,"{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, assetId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON,TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(ctx.getAssetService()).thenReturn(assetService); when(assetService.findAssetByIdAsync(any(), eq(assetId))).thenReturn(Futures.immediateFuture(null)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java index a2bfb26b4e..5d8901dcad 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +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.TbMsgCallback; @@ -42,7 +43,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbCopyKeysNodeTest { DeviceId deviceId; @@ -158,7 +158,7 @@ public class TbCopyKeysNodeTest { "voltageDataValue", "220", "city", "NY" ); - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java index c73e1eec15..838f35e25e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +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.TbMsgCallback; @@ -42,7 +43,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbDeleteKeysNodeTest { DeviceId deviceId; @@ -141,7 +141,7 @@ public class TbDeleteKeysNodeTest { "voltageDataValue", "220", "city", "NY" ); - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java index 91db8dfd26..a86d902007 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java @@ -27,6 +27,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +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.TbMsgCallback; @@ -42,7 +43,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbJsonPathNodeTest { DeviceId deviceId; @@ -171,6 +171,6 @@ public class TbJsonPathNodeTest { Map mdMap = Map.of("country", "US", "city", "NY" ); - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java index ba17187297..be6aea6fa5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.id.DeviceId; 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.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -65,14 +66,10 @@ 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.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @Slf4j public class TbMsgDeduplicationNodeTest { - private static final String TB_MSG_DEDUPLICATION_TIMEOUT_MSG = "TbMsgDeduplicationNodeMsg"; - private TbContext ctx; private final ThingsBoardThreadFactory factory = ThingsBoardThreadFactory.forName("de-duplication-node-test"); @@ -98,12 +95,12 @@ public class TbMsgDeduplicationNodeTest { when(ctx.getTenantId()).thenReturn(tenantId); doAnswer((Answer) invocationOnMock -> { - String type = (String) (invocationOnMock.getArguments())[1]; + TbMsgType type = (TbMsgType) (invocationOnMock.getArguments())[1]; EntityId originator = (EntityId) (invocationOnMock.getArguments())[2]; TbMsgMetaData metaData = (TbMsgMetaData) (invocationOnMock.getArguments())[3]; String data = (String) (invocationOnMock.getArguments())[4]; return TbMsg.newMsg(type, originator, metaData.copy(), data); - }).when(ctx).newMsg(isNull(), eq(TB_MSG_DEDUPLICATION_TIMEOUT_MSG), nullable(EntityId.class), any(TbMsgMetaData.class), any(String.class)); + }).when(ctx).newMsg(isNull(), eq(TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG), nullable(EntityId.class), any(TbMsgMetaData.class), any(String.class)); node = spy(new TbMsgDeduplicationNode()); config = new TbMsgDeduplicationNodeConfiguration().defaultConfiguration(); } @@ -243,7 +240,7 @@ public class TbMsgDeduplicationNodeTest { config.setInterval(deduplicationInterval); config.setStrategy(DeduplicationStrategy.ALL); - config.setOutMsgType(POST_ATTRIBUTES_REQUEST.name()); + config.setOutMsgType(TbMsgType.POST_ATTRIBUTES_REQUEST.name()); config.setQueueName(DataConstants.HP_QUEUE_NAME); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -283,7 +280,7 @@ public class TbMsgDeduplicationNodeTest { config.setInterval(deduplicationInterval); config.setStrategy(DeduplicationStrategy.ALL); - config.setOutMsgType(POST_ATTRIBUTES_REQUEST.name()); + config.setOutMsgType(TbMsgType.POST_ATTRIBUTES_REQUEST.name()); config.setQueueName(DataConstants.HP_QUEUE_NAME); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -415,7 +412,7 @@ public class TbMsgDeduplicationNodeTest { metaData.putValue("ts", String.valueOf(ts)); return TbMsg.newMsg( DataConstants.MAIN_QUEUE_NAME, - POST_TELEMETRY_REQUEST.name(), + TbMsgType.POST_TELEMETRY_REQUEST, deviceId, metaData, JacksonUtil.toString(dataNode)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java index 0caa7f74f3..f226f2e166 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +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.TbMsgCallback; @@ -40,7 +41,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbRenameKeysNodeTest { DeviceId deviceId; @@ -155,6 +155,6 @@ public class TbRenameKeysNodeTest { "country", "US", "city", "NY" ); - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java index cf1eee085c..5cdb0d305b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java @@ -27,6 +27,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +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.TbMsgCallback; @@ -43,7 +44,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbSplitArrayMsgNodeTest { DeviceId deviceId; @@ -133,6 +133,6 @@ public class TbSplitArrayMsgNodeTest { "country", "US", "city", "NY" ); - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java index 71348a1a23..56c94fd4ff 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -60,8 +61,8 @@ public class TbTransformMsgNodeTest { RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON,rawJson, ruleChainId, ruleNodeId); - TbMsg transformedMsg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, "{new}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, metaData, TbMsgDataType.JSON,rawJson, ruleChainId, ruleNodeId); + TbMsg transformedMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, metaData, TbMsgDataType.JSON, "{new}", ruleChainId, ruleNodeId); when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFuture(Collections.singletonList(transformedMsg))); node.onMsg(ctx, msg); @@ -80,7 +81,7 @@ public class TbTransformMsgNodeTest { RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFailedFuture(new IllegalStateException("error"))); node.onMsg(ctx, msg); From 4528348143b67e14d502069cac0d663d72ecdfac Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 6 Jul 2023 13:18:02 +0300 Subject: [PATCH 034/168] replaced new TbMsgMetaData() with TbMsgMetaData.EMPTY and added additional refactoring after review of changes --- .../server/common/data/msg/TbMsgType.java | 6 ---- .../server/common/data/msg/TbMsgTypeTest.java | 1 - .../rule/engine/debug/TbMsgGeneratorNode.java | 2 +- .../engine/metadata/TbGetTelemetryNode.java | 12 ++++--- .../rule/engine/profile/DeviceState.java | 31 ++++++------------- .../engine/profile/TbDeviceProfileNode.java | 2 +- .../action/TbCreateRelationNodeTest.java | 2 +- .../engine/edge/TbMsgPushToEdgeNodeTest.java | 2 +- .../engine/filter/TbJsFilterNodeTest.java | 2 +- .../geo/TbGpsGeofencingFilterNodeTest.java | 2 +- .../rule/engine/math/TbMathNodeTest.java | 26 ++++++++-------- .../metadata/CalculateDeltaNodeTest.java | 3 +- .../metadata/TbGetAttributesNodeTest.java | 2 +- .../TbGetCustomerAttributeNodeTest.java | 2 +- .../TbGetCustomerDetailsNodeTest.java | 2 +- .../TbGetOriginatorFieldsNodeTest.java | 2 +- .../TbGetRelatedAttributeNodeTest.java | 2 +- .../TbGetTenantAttributeNodeTest.java | 2 +- .../metadata/TbGetTenantDetailsNodeTest.java | 2 +- .../rule/engine/profile/DeviceStateTest.java | 4 +-- .../rule/engine/rest/TbHttpClientTest.java | 2 +- .../engine/transform/TbCopyKeysNodeTest.java | 6 ++-- .../transform/TbDeleteKeysNodeTest.java | 3 +- .../transform/TbRenameKeysNodeTest.java | 3 +- .../transform/TbSplitArrayMsgNodeTest.java | 3 +- 25 files changed, 52 insertions(+), 74 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 0084b391eb..206b203682 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 @@ -71,19 +71,13 @@ public enum TbMsgType { // tellSelfOnly types GENERATOR_NODE_SELF_MSG(null, true), - DEVICE_PROFILE_PERIODIC_SELF_MSG(null, true), DEVICE_PROFILE_UPDATE_SELF_MSG(null, true), DEVICE_UPDATE_SELF_MSG(null, true), - DEDUPLICATION_TIMEOUT_SELF_MSG(null, true), - DELAY_TIMEOUT_SELF_MSG(null, true), - MSG_COUNT_SELF_MSG(null, true); - - public static final List NODE_CONNECTIONS = EnumSet.allOf(TbMsgType.class).stream() .filter(tbMsgType -> !tbMsgType.isTellSelfOnly()) .map(TbMsgType::getRuleNodeConnection) 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 58f2089aed..1323b7359d 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 @@ -54,7 +54,6 @@ class TbMsgTypeTest { MSG_COUNT_SELF_MSG ); - // backward-compatibility tests @Test diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 7c3d6424e5..2f1aae5000 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -136,7 +136,7 @@ public class TbMsgGeneratorNode implements TbNode { } lastScheduledTs = lastScheduledTs + delay; long curDelay = Math.max(0L, (lastScheduledTs - curTs)); - TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TbMsgType.GENERATOR_NODE_SELF_MSG, ctx.getSelfId(), new TbMsgMetaData(), TbMsg.EMPTY_STRING); + TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TbMsgType.GENERATOR_NODE_SELF_MSG, ctx.getSelfId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); nextTickId = tickMsg.getId(); ctx.tellSelf(tickMsg, curDelay); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index 4cf564fd7b..a7ddf2a76b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; import java.util.List; import java.util.concurrent.ExecutionException; @@ -98,8 +99,8 @@ public class TbGetTelemetryNode implements TbNode { List keys = TbNodeUtils.processPatterns(tsKeyNames, msg); ListenableFuture> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(interval, keys)); DonAsynchron.withCallback(list, data -> { - process(data, msg, keys); - ctx.tellSuccess(msg); + var metaData = updateMetadata(data, msg, keys); + ctx.tellSuccess(TbMsg.transformMsg(msg, metaData)); }, error -> ctx.tellFailure(msg, error), ctx.getDbCallbackExecutor()); } catch (Exception e) { ctx.tellFailure(msg, e); @@ -129,19 +130,20 @@ public class TbGetTelemetryNode implements TbNode { } } - private void process(List entries, TbMsg msg, List keys) { + private TbMsgMetaData updateMetadata(List entries, TbMsg msg, List keys) { ObjectNode resultNode = JacksonUtil.newObjectNode(JacksonUtil.ALLOW_UNQUOTED_FIELD_NAMES_MAPPER); if (TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL.equals(fetchMode)) { entries.forEach(entry -> processArray(resultNode, entry)); } else { entries.forEach(entry -> processSingle(resultNode, entry)); } - + var copy = msg.getMetaData().copy(); for (String key : keys) { if (resultNode.has(key)) { - msg.getMetaData().putValue(key, resultNode.get(key).toString()); + copy.putValue(key, resultNode.get(key).toString()); } } + return copy; } private void processSingle(ObjectNode node, TsKvEntry entry) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index e368358299..8cccc51258 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.rule.RuleNodeState; @@ -54,18 +55,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_ACK; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_CLEAR; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; -import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; -import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UNASSIGNED; -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; - @Slf4j class DeviceState { @@ -147,24 +136,24 @@ class DeviceState { latestValues = fetchLatestValues(ctx, deviceId); } boolean stateChanged = false; - if (msg.getType().equals(POST_TELEMETRY_REQUEST.name())) { + if (msg.getType().equals(TbMsgType.POST_TELEMETRY_REQUEST.name())) { stateChanged = processTelemetry(ctx, msg); - } else if (msg.getType().equals(POST_ATTRIBUTES_REQUEST.name())) { + } else if (msg.getType().equals(TbMsgType.POST_ATTRIBUTES_REQUEST.name())) { stateChanged = processAttributesUpdateRequest(ctx, msg); - } else if (msg.getType().equals(ACTIVITY_EVENT.name()) || msg.getType().equals(INACTIVITY_EVENT.name())) { + } else if (msg.getType().equals(TbMsgType.ACTIVITY_EVENT.name()) || msg.getType().equals(TbMsgType.INACTIVITY_EVENT.name())) { stateChanged = processDeviceActivityEvent(ctx, msg); - } else if (msg.getType().equals(ATTRIBUTES_UPDATED.name())) { + } else if (msg.getType().equals(TbMsgType.ATTRIBUTES_UPDATED.name())) { stateChanged = processAttributesUpdateNotification(ctx, msg); - } else if (msg.getType().equals(ATTRIBUTES_DELETED.name())) { + } else if (msg.getType().equals(TbMsgType.ATTRIBUTES_DELETED.name())) { stateChanged = processAttributesDeleteNotification(ctx, msg); - } else if (msg.getType().equals(ALARM_CLEAR.name())) { + } else if (msg.getType().equals(TbMsgType.ALARM_CLEAR.name())) { stateChanged = processAlarmClearNotification(ctx, msg); - } else if (msg.getType().equals(ALARM_ACK.name())) { + } else if (msg.getType().equals(TbMsgType.ALARM_ACK.name())) { processAlarmAckNotification(ctx, msg); - } else if (msg.getType().equals(ALARM_DELETE.name())) { + } else if (msg.getType().equals(TbMsgType.ALARM_DELETE.name())) { processAlarmDeleteNotification(ctx, msg); } else { - if (msg.getType().equals(ENTITY_ASSIGNED.name()) || msg.getType().equals(ENTITY_UNASSIGNED.name())) { + if (msg.getType().equals(TbMsgType.ENTITY_ASSIGNED.name()) || msg.getType().equals(TbMsgType.ENTITY_UNASSIGNED.name())) { dynamicPredicateValueCtx.resetCustomer(); } ctx.tellSuccess(msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 0abb15f279..0475728c4d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -170,7 +170,7 @@ public class TbDeviceProfileNode implements TbNode { } protected void scheduleAlarmHarvesting(TbContext ctx, TbMsg msg) { - TbMsg periodicCheck = TbMsg.newMsg(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG, ctx.getTenantId(), msg != null ? msg.getCustomerId() : null, TbMsgMetaData.EMPTY, "{}"); + TbMsg periodicCheck = TbMsg.newMsg(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG, ctx.getTenantId(), msg != null ? msg.getCustomerId() : null, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); ctx.tellSelf(periodicCheck, TimeUnit.MINUTES.toMillis(1)); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java index 7a9d95cad4..f36cc94540 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java @@ -122,7 +122,7 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); EntityRelation relation = new EntityRelation(); when(ctx.getRelationService().findByToAndTypeAsync(any(), eq(msg.getOriginator()), eq(EntityRelation.CONTAINS_TYPE), eq(RelationTypeGroup.COMMON))) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java index 0c51462429..f44326a8d5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java @@ -125,7 +125,7 @@ public class TbMsgPushToEdgeNodeTest { @Test public void testMiscEventsProcessedAsTimeseriesUpdated() { for (var event : MISC_EVENTS) { - testEvent(event, new TbMsgMetaData(), EdgeEventActionType.TIMESERIES_UPDATED, "data"); + testEvent(event, TbMsgMetaData.EMPTY, EdgeEventActionType.TIMESERIES_UPDATED, "data"); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 6ce33fd009..639c1a7b1a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -59,7 +59,7 @@ public class TbJsFilterNodeTest { @Test public void falseEvaluationDoNotSendMsg() throws TbNodeException { initWithScript(); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, new TbMsgMetaData(), TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(false)); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java index 64a239d57f..f4585d0f96 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java @@ -452,7 +452,7 @@ class TbGpsGeofencingFilterNodeTest { } private TbMsg getEmptyArrayTbMsg(EntityId entityId) { - return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, "[]"); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java index 3e19c7a736..0a42349433 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java @@ -247,7 +247,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).put("b", arg2).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", arg1).put("b", arg2).toString()); node.onMsg(ctx, msg); @@ -270,7 +270,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", arg1).toString()); node.onMsg(ctx, msg); @@ -293,7 +293,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); node.onMsg(ctx, msg); @@ -316,7 +316,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); node.onMsg(ctx, msg); @@ -340,7 +340,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.TIME_SERIES, "b") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().toString()); Mockito.when(attributesService.find(tenantId, originator, DataConstants.SERVER_SCOPE, "a")) .thenReturn(Futures.immediateFuture(Optional.of(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("a", 2.0))))); @@ -368,7 +368,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); node.onMsg(ctx, msg); @@ -390,7 +390,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); node.onMsg(ctx, msg); @@ -412,7 +412,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); Mockito.when(telemetryService.saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble())) .thenReturn(Futures.immediateFuture(null)); @@ -438,7 +438,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) .thenReturn(Futures.immediateFuture(null)); @@ -463,7 +463,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) .thenReturn(Futures.immediateFuture(null)); @@ -494,7 +494,7 @@ public class TbMathNodeTest { new TbMathResult(TbMathArgumentType.MESSAGE_METADATA, "result", 3, false, false, null), tbMathArgument ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 10).toString()); node.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -514,7 +514,7 @@ public class TbMathNodeTest { new TbMathResult(TbMathArgumentType.TIME_SERIES, "result", 3, true, false, DataConstants.SERVER_SCOPE), new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 10).toString()); Throwable thrown = assertThrows(RuntimeException.class, () -> { node.onMsg(ctx, msg); }); @@ -528,7 +528,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), "[]"); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); Throwable thrown = assertThrows(RuntimeException.class, () -> { node.onMsg(ctx, msg); }); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java index 9dbae201ac..2f99fbb43c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java @@ -117,8 +117,7 @@ public class CalculateDeltaNodeTest { @Test public void givenInvalidMsgDataType_whenOnMsg_thenShouldTellNextOther() { // GIVEN - var msgData = "[]"; - var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN node.onMsg(ctxMock, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java index a889f91b77..2263b272b9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java @@ -241,7 +241,7 @@ public class TbGetAttributesNodeTest { public void givenFetchLatestTimeseriesToDataAndDataIsNotJsonObject_whenOnMsg_thenException() throws Exception { // GIVEN node = initNode(FetchTo.DATA, true, true); - var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 3a8f36f5dc..b413d2c576 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -208,7 +208,7 @@ public class TbGetCustomerAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index 8b958bcc3c..1908578801 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -157,7 +157,7 @@ public class TbGetCustomerDetailsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index 0057ec7c61..dfc80d1752 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -133,7 +133,7 @@ public class TbGetOriginatorFieldsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index 6b3a225eac..0965327df0 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -222,7 +222,7 @@ public class TbGetRelatedAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index ffb053c93a..c7a23a512b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -188,7 +188,7 @@ public class TbGetTenantAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java index 2dda9ffaf0..23b1abea73 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java @@ -127,7 +127,7 @@ public class TbGetTenantDetailsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java index 6bc4a59610..eef77a5304 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java @@ -107,7 +107,7 @@ public class DeviceStateTest { DeviceState deviceState = createDeviceState(deviceId, alarmConfig); TbMsg attributeUpdateMsg = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, - deviceId, new TbMsgMetaData(), "{ \"enabled\": false }"); + deviceId, TbMsgMetaData.EMPTY, "{ \"enabled\": false }"); deviceState.process(ctx, attributeUpdateMsg); @@ -119,7 +119,7 @@ public class DeviceStateTest { reset(ctx); String deletedAttributes = "{ \"attributes\": [ \"other\" ] }"; - deviceState.process(ctx, TbMsg.newMsg(TbMsgType.ATTRIBUTES_DELETED, deviceId, new TbMsgMetaData(), deletedAttributes)); + deviceState.process(ctx, TbMsg.newMsg(TbMsgType.ATTRIBUTES_DELETED, deviceId, TbMsgMetaData.EMPTY, deletedAttributes)); verify(ctx, never()).enqueueForTellNext(any(), anyString()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java index 2ddc20d5ed..48aca3b573 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java @@ -145,7 +145,7 @@ public class TbHttpClientTest { var httpClient = new TbHttpClient(config, eventLoop); httpClient.setHttpClient(asyncRestTemplate); - var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, new DeviceId(EntityId.NULL_UUID), TbMsgMetaData.EMPTY, "{}"); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, new DeviceId(EntityId.NULL_UUID), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); var successMsg = TbMsg.newMsg( TbMsgType.POST_TELEMETRY_REQUEST, msg.getOriginator(), msg.getMetaData(), msg.getData() diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java index 5d8901dcad..e0ab34e35b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java @@ -79,8 +79,7 @@ public class TbCopyKeysNodeTest { @Test void givenMsgFromMetadata_whenOnMsg_thenVerifyOutput() throws Exception { - String data = "{}"; - node.onMsg(ctx, getTbMsg(deviceId, data)); + node.onMsg(ctx, getTbMsg(deviceId, TbMsg.EMPTY_JSON_OBJECT)); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); @@ -137,8 +136,7 @@ public class TbCopyKeysNodeTest { @Test void givenMsgDataNotJSONObject_whenOnMsg_thenTVerifyOutput() throws Exception { - String data = "[]"; - TbMsg msg = getTbMsg(deviceId, data); + TbMsg msg = getTbMsg(deviceId, TbMsg.EMPTY_JSON_ARRAY); node.onMsg(ctx, msg); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java index 838f35e25e..6eee16e7d3 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java @@ -79,8 +79,7 @@ public class TbDeleteKeysNodeTest { @Test void givenMsgFromMetadata_whenOnMsg_thenVerifyOutput() throws Exception { - String data = "{}"; - node.onMsg(ctx, getTbMsg(deviceId, data)); + node.onMsg(ctx, getTbMsg(deviceId, TbMsg.EMPTY_JSON_OBJECT)); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java index f226f2e166..c602aab5df 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java @@ -135,8 +135,7 @@ public class TbRenameKeysNodeTest { @Test void givenMsgDataNotJSONObject_whenOnMsg_thenVerifyOutput() throws Exception { - String data = "[]"; - TbMsg msg = getTbMsg(deviceId, data); + TbMsg msg = getTbMsg(deviceId, TbMsg.EMPTY_JSON_ARRAY); node.onMsg(ctx, msg); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java index 5cdb0d305b..e7e2ae16b2 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java @@ -83,8 +83,7 @@ public class TbSplitArrayMsgNodeTest { @Test void givenZeroMsg_whenOnMsg_thenVerifyOutput() throws Exception { - String data = "[]"; - VerifyOutputMsg(data); + VerifyOutputMsg(TbMsg.EMPTY_JSON_ARRAY); } @Test From ffdb16766ce033a4002bad6a4675693178230178 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 6 Jul 2023 13:31:25 +0200 Subject: [PATCH 035/168] improvements --- .../queue/discovery/ZkDiscoveryService.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 17d046a4cb..24a7863b24 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -299,16 +299,16 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi case CHILD_ADDED: ScheduledFuture task = delayedTasks.remove(instance.getServiceId()); if (task != null) { - if (!task.cancel(false)) { - log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + if (task.cancel(false)) { + log.debug("[{}] Recalculate partitions ignored. Service was restarted in time [{}].", instance.getServiceId(), instance.getServiceTypesList()); - recalculatePartitions(); } else { - log.debug("[{}] Recalculate partitions ignored. Service restarted in time [{}]", + log.debug("[{}] Going to recalculate partitions. Service was not restarted in time [{}]!", instance.getServiceId(), instance.getServiceTypesList()); + recalculatePartitions(); } } else { - log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + log.debug("[{}] Going to recalculate partitions due to adding new node [{}].", instance.getServiceId(), instance.getServiceTypesList()); recalculatePartitions(); } @@ -317,8 +317,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi ScheduledFuture future = zkExecutorService.schedule(() -> { log.debug("[{}] Going to recalculate partitions due to removed node [{}]", instance.getServiceId(), instance.getServiceTypesList()); - delayedTasks.remove(instance.getServiceId()); - recalculatePartitions(); + ScheduledFuture removedTask = delayedTasks.remove(instance.getServiceId()); + if (removedTask != null) { + recalculatePartitions(); + } }, recalculateDelay, TimeUnit.MILLISECONDS); delayedTasks.put(instance.getServiceId(), future); break; @@ -332,6 +334,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi * Synchronized to ensure that other servers info is up to date * */ synchronized void recalculatePartitions() { + delayedTasks.clear(); partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); } From 14216a48827b9f59d836b7362a8d296899c1b0a2 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 6 Jul 2023 15:03:32 +0300 Subject: [PATCH 036/168] fixed security transport cases --- .../src/main/resources/thingsboard.yml | 12 ++ .../server/dao/device/DeviceService.java | 1 + .../dao/device/DeviceConnectivityInfo.java | 5 +- .../server/dao/device/DeviceServiceImpl.java | 126 ++++++++++++------ 4 files changed, 97 insertions(+), 47 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 2f58590bf6..3615e25825 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -981,16 +981,28 @@ transport: # Device connectivity properties to publish telemetry device: connectivity: + http: + enabled: "${DEVICE_CONNECTIVITY_HTTP_ENABLED:true}" + host: "${DEVICE_CONNECTIVITY_HTTP_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_HTTP_PORT:8080}" + https: + enabled: "${DEVICE_CONNECTIVITY_HTTPS_ENABLED:false}" + host: "${DEVICE_CONNECTIVITY_HTTPS_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_HTTPS_PORT:443}" mqtt: + enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:true}" host: "${DEVICE_CONNECTIVITY_MQTT_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_MQTT_PORT:1883}" mqtts: + enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:false}" host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_MQTTS_PORT:8883}" coap: + enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:true}" host: "${DEVICE_CONNECTIVITY_COAP_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_COAP_PORT:5683}" coaps: + enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:false}" host: "${DEVICE_CONNECTIVITY_COAPS_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_COAPS_PORT:5684}" diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index 72c6a8852c..a029f27309 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.device; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java index 7b477bfc42..f570919290 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java @@ -16,11 +16,10 @@ package org.thingsboard.server.dao.device; import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - @Data public class DeviceConnectivityInfo { + private Boolean enabled; private String host; - private Integer port; + private String port; } 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 fe5ac33e73..1a881d82bd 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 @@ -80,6 +80,8 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -104,8 +106,15 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDevicePublishTelemetryCommands(String baseUrl, Device device) { + public Map findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException { DeviceId deviceId = device.getId(); log.trace("Executing findDevicePublishTelemetryCommands [{}]", deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); + String defaultHostname = new URI(baseUrl).getHost(); DeviceCredentials creds = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); - DeviceCredentialsType credentialsType = creds.getCredentialsType(); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); DeviceTransportType transportType = deviceProfile.getTransportType(); Map commands = new HashMap<>(); - switch (transportType) { case DEFAULT: - Optional.ofNullable(getHttpPublishCommand(baseUrl, creds)).ifPresent(v -> commands.put("http", v)); - Optional.ofNullable(getMqttPublishCommand(creds)).ifPresent(v -> commands.put("mqtt", v)); - Optional.ofNullable(getMqttsPublishCommand(creds)).ifPresent(v -> commands.put("mqtts", v)); - Optional.ofNullable(getCoapPublishCommand(creds)).ifPresent(v -> commands.put("coap", v)); - Optional.ofNullable(getCoapsPublishCommand(creds)).ifPresent(v -> commands.put("coaps", v)); + Optional.ofNullable(getHttpPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(HTTP_PROTOCOL, v)); + Optional.ofNullable(getHttpsPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(HTTPS_PROTOCOL, v)); + Optional.ofNullable(getMqttPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(MQTT_PROTOCOL, v)); + Optional.ofNullable(getMqttsPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(MQTTS_PROTOCOL, v)); + Optional.ofNullable(getCoapPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(COAP_PROTOCOL, v)); + Optional.ofNullable(getCoapsPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(COAPS_PROTOCOL, v)); break; case MQTT: MqttDeviceProfileTransportConfiguration transportConfiguration = @@ -164,19 +172,12 @@ public class DeviceServiceImpl extends AbstractCachedEntityService commands.put("mqtt", v)); - Optional.ofNullable(getMqttsPublishCommand(topicName, creds, payload)).ifPresent(v -> commands.put("mqtts", v)); + Optional.ofNullable(getMqttPublishCommand(defaultHostname, topicName, creds, payload)).ifPresent(v -> commands.put(MQTT_PROTOCOL, v)); + Optional.ofNullable(getMqttsPublishCommand(defaultHostname, topicName, creds, payload)).ifPresent(v -> commands.put(MQTTS_PROTOCOL, v)); break; case COAP: - CoapDeviceProfileTransportConfiguration coapTransportConfiguration = - (CoapDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); - CoapDeviceTypeConfiguration coapConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); - if (coapConfiguration instanceof DefaultCoapDeviceTypeConfiguration) { - Optional.ofNullable(getCoapPublishCommand(creds)).ifPresent(v -> commands.put("coap", v)); - Optional.ofNullable(getCoapsPublishCommand(creds)).ifPresent(v -> commands.put("coaps", v)); - } else if (coapConfiguration instanceof EfentoCoapDeviceTypeConfiguration) { - commands.put("coap for efento", "Not supported"); - } + Optional.ofNullable(getCoapPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(COAP_PROTOCOL, v)); + Optional.ofNullable(getCoapsPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(COAPS_PROTOCOL, v)); break; default: commands.put(transportType.name(), NOT_SUPPORTED); @@ -743,24 +744,45 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Thu, 6 Jul 2023 15:06:07 +0300 Subject: [PATCH 037/168] minor refactoring --- .../server/dao/device/DeviceServiceImpl.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 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 1a881d82bd..a0a8b9dbcd 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 @@ -48,10 +48,6 @@ import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; -import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; -import org.thingsboard.server.common.data.device.profile.EfentoCoapDeviceTypeConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; @@ -745,7 +741,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Thu, 6 Jul 2023 15:18:16 +0300 Subject: [PATCH 038/168] fixed descriptions for enrichment rule nodes --- .../rule/engine/filter/TbOriginatorTypeSwitchNode.java | 2 +- .../thingsboard/rule/engine/metadata/CalculateDeltaNode.java | 3 ++- .../rule/engine/metadata/TbFetchDeviceCredentialsNode.java | 4 +++- .../thingsboard/rule/engine/metadata/TbGetAttributesNode.java | 3 ++- .../rule/engine/metadata/TbGetCustomerAttributeNode.java | 3 ++- .../rule/engine/metadata/TbGetCustomerDetailsNode.java | 3 ++- .../thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java | 4 +++- .../rule/engine/metadata/TbGetOriginatorFieldsNode.java | 3 ++- .../rule/engine/metadata/TbGetRelatedAttributeNode.java | 3 ++- .../thingsboard/rule/engine/metadata/TbGetTelemetryNode.java | 3 ++- .../rule/engine/metadata/TbGetTenantAttributeNode.java | 3 ++- .../rule/engine/metadata/TbGetTenantDetailsNode.java | 3 ++- 12 files changed, 25 insertions(+), 12 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java index 9e14c6a7ab..17b7d575f5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; relationTypes = {}, // should always be empty. We add the relation types for this node in AnnotationComponentDiscoveryService. nodeDescription = "Route incoming messages by Message Originator Type", nodeDetails = "Routes messages to chain according to the entity type ('Device', 'Asset', etc.).

" + - "Output connections: Message originator type or Failure", + "Output connections: Message originator type or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbOriginatorTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index 25b9205602..3e4e6eb93f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -50,7 +50,8 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; configClazz = CalculateDeltaNodeConfiguration.class, nodeDescription = "Calculates delta and amount of time passed between previous timeseries key reading " + "and current value for this key from the incoming message", - nodeDetails = "Useful for metering use cases, when you need to calculate consumption based on pulse counter reading.", + nodeDetails = "Useful for metering use cases, when you need to calculate consumption based on pulse counter reading.

" + + "Output connections: Success, Other or Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeCalculateDeltaConfig") public class CalculateDeltaNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java index e23da1e364..d8934535af 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java @@ -41,7 +41,9 @@ import java.util.concurrent.ExecutionException; nodeDescription = "Adds device credentials to the message or message metadata", nodeDetails = "if message originator type is Device and device credentials was successfully fetched, " + "rule node enriches message or message metadata with credentialsType and credentials properties. " + - "Useful when you need to fetch device credentials and use them for further message processing. For example, use device credentials to interact with external systems.", + "Useful when you need to fetch device credentials and use them for further message processing. " + + "For example, use device credentials to interact with external systems.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeFetchDeviceCredentialsConfig") public class TbFetchDeviceCredentialsNode extends TbAbstractNodeWithFetchTo { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 975da84510..edc7bdc44e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -40,7 +40,8 @@ import org.thingsboard.server.common.msg.TbMsg; nodeDescription = "Adds attributes and/or latest timeseries data for the message originator to the message or message metadata", nodeDetails = "Useful when you need to retrieve some attributes or the latest telemetry readings from the message originator " + "that are not included in the incoming message to use them for further message processing. " + - "For example to filter messages based on the threshold value stored in the attributes.", + "For example to filter messages based on the threshold value stored in the attributes.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeOriginatorAttributesConfig") public class TbGetAttributesNode extends TbAbstractGetAttributesNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index f5b50f259c..088aa596fc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -39,7 +39,8 @@ import org.thingsboard.server.common.data.util.TbPair; nodeDescription = "Adds message originator customer attributes or latest telemetry into message or message metadata", nodeDetails = "Useful in multi-customer solutions where each customer has a different configuration or threshold set " + "that is stored as customer attributes or telemetry data and used for dynamic message filtering, transformation, " + - "or actions such as alarm creation if the threshold is exceeded.", + "or actions such as alarm creation if the threshold is exceeded.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeCustomerAttributesConfig") public class TbGetCustomerAttributeNode extends TbAbstractGetEntityDataNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java index 3be5ac15c3..377e9eb0da 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java @@ -47,7 +47,8 @@ import java.util.NoSuchElementException; version = 1, nodeDescription = "Adds message originator customer details into message or message metadata", nodeDetails = "Useful in multi-customer solutions where we need dynamically use customer contact information " + - "such as email, phone, address, etc., for notifications via email, SMS, and other notification providers.", + "such as email, phone, address, etc., for notifications via email, SMS, and other notification providers.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeEntityDetailsConfig") public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java index 67e544fcfa..eb82d26007 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java @@ -38,7 +38,9 @@ import org.thingsboard.server.common.msg.TbMsg; nodeDescription = "Add originators related device attributes and/or latest telemetry values into message or message metadata", nodeDetails = "Related device lookup based on the configured relation query. " + "If multiple related devices are found, only first device is used for message enrichment, other entities are discarded. " + - "Useful when you need to retrieve attributes and/or latest telemetry values from device that has a relation to the message originator and use them for further message processing.", + "Useful when you need to retrieve attributes and/or latest telemetry values from device that has a relation " + + "to the message originator and use them for further message processing.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeDeviceAttributesConfig") public class TbGetDeviceAttrNode extends TbAbstractGetAttributesNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java index 34153360a3..30c3e10cde 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java @@ -40,7 +40,8 @@ import java.util.concurrent.ExecutionException; version = 1, nodeDescription = "Adds message originator fields values into message or message metadata", nodeDetails = "Fetches fields values specified in the mapping. If specified field is not part of originator fields it will be ignored. " + - "Useful when you need to retrieve originator fields and use them for further message processing.", + "Useful when you need to retrieve originator fields and use them for further message processing.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeOriginatorFieldsConfig") public class TbGetOriginatorFieldsNode extends TbAbstractGetMappedDataNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index 249caa87ac..50e75c8cc0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -40,7 +40,8 @@ import java.util.Arrays; nodeDescription = "Adds originators related entity attributes or latest telemetry or fields into message or message metadata", nodeDetails = "Related entity lookup based on the configured relation query. " + "If multiple related entities are found, only first entity is used for message enrichment, other entities are discarded. " + - "Useful when you need to retrieve data from an entity that has a relation to the message originator and use them for further message processing.", + "Useful when you need to retrieve data from an entity that has a relation to the message originator and use them for further message processing.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeRelatedAttributesConfig") public class TbGetRelatedAttributeNode extends TbAbstractGetEntityDataNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index a7ddf2a76b..08fa2a6246 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -54,7 +54,8 @@ import java.util.stream.Collectors; nodeDescription = "Adds message originator telemetry for selected time range into message metadata", nodeDetails = "Useful when you need to get telemetry data set from the message originator for a specific time range " + "instead of fetching just the latest telemetry or if you need to get the closest telemetry to the fetch interval start or end. " + - "Also, this node can be used for telemetry aggregation within configured fetch interval.", + "Also, this node can be used for telemetry aggregation within configured fetch interval.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeGetTelemetryFromDatabase") public class TbGetTelemetryNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index cbbf00ce92..bcd54cd829 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -37,7 +37,8 @@ import org.thingsboard.server.common.data.util.TbPair; version = 1, nodeDescription = "Adds message originator tenant attributes or latest telemetry into message or message metadata", nodeDetails = "Useful when you need to retrieve some common configuration or threshold set " + - "that is stored as tenant attributes or telemetry data and use it for further message processing.", + "that is stored as tenant attributes or telemetry data and use it for further message processing.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeTenantAttributesConfig") public class TbGetTenantAttributeNode extends TbAbstractGetEntityDataNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java index 8e293aea37..2a5039f849 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java @@ -36,7 +36,8 @@ import org.thingsboard.server.common.msg.TbMsg; version = 1, nodeDescription = "Adds message originator tenant details into message or message metadata", nodeDetails = "Useful when we need to retrieve contact information from your tenant " + - "such as email, phone, address, etc., for notifications via email, SMS, and other notification providers.", + "such as email, phone, address, etc., for notifications via email, SMS, and other notification providers.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeEntityDetailsConfig") public class TbGetTenantDetailsNode extends TbAbstractGetEntityDetailsNode { From bb71ae39c3ed381241e2606202f9e27dd92d9de1 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 6 Jul 2023 17:24:13 +0300 Subject: [PATCH 039/168] added tests --- .../controller/DeviceControllerTest.java | 175 +++++++++++++++--- 1 file changed, 150 insertions(+), 25 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 96aa5638db..477500508f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -34,6 +34,7 @@ import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; @@ -51,14 +52,16 @@ 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.device.credentials.BasicMqttCredentials; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceCredentialsId; 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.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -91,12 +94,19 @@ import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +@TestPropertySource(properties = { + "device.connectivity.https.enabled=true", + "device.connectivity.mqtts.enabled=true", + "device.connectivity.coaps.enabled=true", +}) @ContextConfiguration(classes = {DeviceControllerTest.Config.class}) @DaoSqlTest public class DeviceControllerTest extends AbstractControllerTest { static final TypeReference> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() { }; + private static final String DEVICE_TELEMETRY_TOPIC = "v1/devices/customTopic"; + ListeningExecutorService executor; List> futures; @@ -104,6 +114,8 @@ public class DeviceControllerTest extends AbstractControllerTest { private Tenant savedTenant; private User tenantAdmin; + private DeviceProfileId mqttDeviceProfileId; + private DeviceProfileId coapDeviceProfileId; @SpyBean private GatewayNotificationsService gatewayNotificationsService; @@ -138,6 +150,34 @@ public class DeviceControllerTest extends AbstractControllerTest { tenantAdmin.setLastName("Downs"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + DeviceProfile mqttProfile = new DeviceProfile(); + mqttProfile.setName("Mqtt device profile"); + mqttProfile.setType(DeviceProfileType.DEFAULT); + mqttProfile.setTransportType(DeviceTransportType.MQTT); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); + MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration(); + transportConfiguration.setDeviceTelemetryTopic(DEVICE_TELEMETRY_TOPIC); + deviceProfileData.setTransportConfiguration(transportConfiguration); + mqttProfile.setProfileData(deviceProfileData); + mqttProfile.setDefault(false); + mqttProfile.setDefaultRuleChainId(null); + + mqttDeviceProfileId = doPost("/api/deviceProfile", mqttProfile, DeviceProfile.class).getId(); + + DeviceProfile coapProfile = new DeviceProfile(); + coapProfile.setName("Coap device profile"); + coapProfile.setType(DeviceProfileType.DEFAULT); + coapProfile.setTransportType(DeviceTransportType.COAP); + DeviceProfileData deviceProfileData2 = new DeviceProfileData(); + deviceProfileData2.setConfiguration(new DefaultDeviceProfileConfiguration()); + deviceProfileData2.setTransportConfiguration(new CoapDeviceProfileTransportConfiguration()); + coapProfile.setProfileData(deviceProfileData); + coapProfile.setDefault(false); + coapProfile.setDefaultRuleChainId(null); + + coapDeviceProfileId = doPost("/api/deviceProfile", coapProfile, DeviceProfile.class).getId(); } @After @@ -655,51 +695,136 @@ public class DeviceControllerTest extends AbstractControllerTest { device.setName("My device"); device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); - List commands = + Map commands = doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); DeviceCredentials credentials = doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - assertThat(commands).hasSize(3); - assertThat(commands).containsExactly(String.format("mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", - credentials.getCredentialsId()), - String.format("curl -v -X POST http://localhost:80/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", - credentials.getCredentialsId()), - String.format("echo -n \"{temperature:25}\" | coap-client -m post coap://localhost:5683/api/v1/%s/telemetry -f-", - credentials.getCredentialsId())); + assertThat(commands).hasSize(6); + assertThat(commands.get("http")).isEqualTo(String.format("curl -v -X POST http://localhost:8080/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(commands.get("https")).isEqualTo(String.format("curl -v -X POST https://localhost:443/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(commands.get("mqtt")).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub --cafile tb-server-chain.pem -d -q 1 -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(commands.get("coap")).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(commands.get("coaps")).isEqualTo(String.format("coap-client-openssl -v 9 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); } @Test - public void testFetchPublishTelemetryCommandsForMqttDevice() throws Exception { - DeviceProfile mqttProfile = new DeviceProfile(); - mqttProfile.setName("Mqtt device profile"); - mqttProfile.setType(DeviceProfileType.DEFAULT); - mqttProfile.setTransportType(DeviceTransportType.MQTT); + public void testFetchPublishTelemetryCommandsForMqttDeviceWithAccessToken() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(mqttDeviceProfileId); - DeviceProfileData deviceProfileData = new DeviceProfileData(); - deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); - deviceProfileData.setTransportConfiguration(new MqttDeviceProfileTransportConfiguration()); + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - mqttProfile.setProfileData(deviceProfileData); - mqttProfile.setDefault(false); - mqttProfile.setDefaultRuleChainId(null); + Map commands = + doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); + assertThat(commands).hasSize(2); + assertThat(commands.get("mqtt")).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub --cafile tb-server-chain.pem -d -q 1 -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + } - mqttProfile = doPost("/api/deviceProfile", mqttProfile, DeviceProfile.class); + @Test + public void testFetchPublishTelemetryCommandsForDeviceWithMqttBasicCreds() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(mqttDeviceProfileId); + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + credentials.setCredentialsId(null); + credentials.setCredentialsType(DeviceCredentialsType.MQTT_BASIC); + BasicMqttCredentials basicMqttCredentials = new BasicMqttCredentials(); + String clientId = "testClientId"; + String userName = "testUsername"; + String password = "testPassword"; + basicMqttCredentials.setClientId(clientId); + basicMqttCredentials.setUserName(userName); + basicMqttCredentials.setPassword(password); + credentials.setCredentialsValue(JacksonUtil.toString(basicMqttCredentials)); + doPost("/api/device/credentials", credentials) + .andExpect(status().isOk()); + + Map commands = + doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); + assertThat(commands).hasSize(2); + assertThat(commands.get("mqtt")).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub --cafile tb-server-chain.pem -d -q 1 -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + } + + @Test + public void testFetchPublishTelemetryCommandsForDeviceWithX509Creds() throws Exception { Device device = new Device(); device.setName("My device"); - device.setDeviceProfileId(mqttProfile.getId()); + device.setDeviceProfileId(mqttDeviceProfileId); Device savedDevice = doPost("/api/device", device, Device.class); DeviceCredentials credentials = doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + credentials.setCredentialsId(null); + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + credentials.setCredentialsValue("testValue"); + doPost("/api/device/credentials", credentials) + .andExpect(status().isOk()); + + Map commands = + doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); + assertThat(commands).hasSize(1); + assertThat(commands.get("mqtts")).isEqualTo("Not supported"); + } + + @Test + public void testFetchPublishTelemetryCommandsForСoapDevice() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(coapDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + Map commands = + doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); + assertThat(commands).hasSize(2); + assertThat(commands.get("coap")).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(commands.get("coaps")).isEqualTo(String.format("coap-client-openssl -v 9 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + } + + @Test + public void testFetchPublishTelemetryCommandsForСoapDeviceWithX509Creds() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(coapDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + credentials.setCredentialsId(null); + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + credentials.setCredentialsValue("testValue"); + doPost("/api/device/credentials", credentials) + .andExpect(status().isOk()); - List commands = + Map commands = doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); assertThat(commands).hasSize(1); - assertThat(commands.get(0)).isEqualTo("mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -u " - + credentials.getCredentialsId() + " -m \"{temperature:25}\""); + assertThat(commands.get("coaps")).isEqualTo("Not supported"); } @Test From a1bf0f6e1911c04e72c3d9ff9b6f5eb8b083930f Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 7 Jul 2023 09:28:56 +0300 Subject: [PATCH 040/168] UI: Add option show/hide table cell actions --- .../widget/lib/alarms-table-widget.component.html | 4 ++-- .../components/widget/lib/alarms-table-widget.component.ts | 5 ++++- .../widget/lib/entities-table-widget.component.html | 4 ++-- .../widget/lib/entities-table-widget.component.ts | 7 +++++-- .../alarm/alarms-table-widget-settings.component.html | 3 +++ .../alarm/alarms-table-widget-settings.component.ts | 2 ++ .../cards/entities-table-widget-settings.component.html | 3 +++ .../cards/entities-table-widget-settings.component.ts | 2 ++ .../cards/timeseries-table-widget-settings.component.html | 3 +++ .../cards/timeseries-table-widget-settings.component.ts | 2 ++ .../home/components/widget/lib/table-widget.models.ts | 1 + .../widget/lib/timeseries-table-widget.component.html | 4 ++-- .../widget/lib/timeseries-table-widget.component.ts | 2 ++ ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 ++- 14 files changed, 35 insertions(+), 10 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html index 9a23e86bf8..cfb92e5b87 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html @@ -125,7 +125,7 @@ -
+
-
+
-
+
-
+
-
+
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.ts index 1bc43044cc..6bd7d62c12 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.ts @@ -47,7 +47,7 @@ export class AlarmsTableWidgetSettingsComponent extends WidgetSettingsComponent enableFilter: true, enableStickyHeader: true, enableStickyAction: true, - hideActionCellButtons: true, + collapseCellActions: true, reserveSpaceForHiddenAction: 'true', displayDetails: true, allowAcknowledgment: true, @@ -70,7 +70,7 @@ export class AlarmsTableWidgetSettingsComponent extends WidgetSettingsComponent enableFilter: [settings.enableFilter, []], enableStickyHeader: [settings.enableStickyHeader, []], enableStickyAction: [settings.enableStickyAction, []], - hideActionCellButtons: [settings.hideActionCellButtons, []], + collapseCellActions: [settings.collapseCellActions, []], reserveSpaceForHiddenAction: [settings.reserveSpaceForHiddenAction, []], displayDetails: [settings.displayDetails, []], allowAcknowledgment: [settings.allowAcknowledgment, []], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html index 2168c190b7..818b18e6ad 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html @@ -66,8 +66,8 @@ {{ 'widgets.table.enable-sticky-action' | translate }} - - {{ 'widgets.table.hide-action-cell-buttons' | translate }} + + {{ 'widgets.table.collapse-cell-actions-mobile' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.ts index 0a030a86a1..f73a86069e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.ts @@ -45,7 +45,7 @@ export class EntitiesTableWidgetSettingsComponent extends WidgetSettingsComponen enableSelectColumnDisplay: true, enableStickyHeader: true, enableStickyAction: true, - hideActionCellButtons: true, + collapseCellActions: true, reserveSpaceForHiddenAction: 'true', displayEntityName: true, entityNameColumnTitle: '', @@ -67,7 +67,7 @@ export class EntitiesTableWidgetSettingsComponent extends WidgetSettingsComponen enableSelectColumnDisplay: [settings.enableSelectColumnDisplay, []], enableStickyHeader: [settings.enableStickyHeader, []], enableStickyAction: [settings.enableStickyAction, []], - hideActionCellButtons: [settings.hideActionCellButtons, []], + collapseCellActions: [settings.collapseCellActions, []], reserveSpaceForHiddenAction: [settings.reserveSpaceForHiddenAction, []], displayEntityName: [settings.displayEntityName, []], entityNameColumnTitle: [settings.entityNameColumnTitle, []], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html index 17e9630082..bb286c83a7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html @@ -39,8 +39,8 @@ {{ 'widgets.table.enable-sticky-action' | translate }} - - {{ 'widgets.table.hide-action-cell-buttons' | translate }} + + {{ 'widgets.table.collapse-cell-actions-mobile' | translate }} widgets.table.hidden-cell-button-display-mode diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts index 3365fac4bb..edec3990e5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts @@ -44,7 +44,7 @@ export class TimeseriesTableWidgetSettingsComponent extends WidgetSettingsCompon enableSelectColumnDisplay: true, enableStickyHeader: true, enableStickyAction: true, - hideActionCellButtons: true, + collapseCellActions: true, reserveSpaceForHiddenAction: 'true', showTimestamp: true, showMilliseconds: false, @@ -64,7 +64,7 @@ export class TimeseriesTableWidgetSettingsComponent extends WidgetSettingsCompon enableSelectColumnDisplay: [settings.enableSelectColumnDisplay, []], enableStickyHeader: [settings.enableStickyHeader, []], enableStickyAction: [settings.enableStickyAction, []], - hideActionCellButtons: [settings.hideActionCellButtons, []], + collapseCellActions: [settings.collapseCellActions, []], reserveSpaceForHiddenAction: [settings.reserveSpaceForHiddenAction, []], showTimestamp: [settings.showTimestamp, []], showMilliseconds: [settings.showMilliseconds, []], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts index 0369d1db68..9de4601e02 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -33,7 +33,7 @@ export interface TableWidgetSettings { enableSearch: boolean; enableSelectColumnDisplay: boolean; enableStickyAction: boolean; - hideActionCellButtons: boolean; + collapseCellActions: boolean; enableStickyHeader: boolean; displayPagination: boolean; defaultPageSize: number; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html index 266d857efb..633b1c7455 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html @@ -67,7 +67,7 @@ -
+
-
+
+
Date: Mon, 10 Jul 2023 12:14:55 +0300 Subject: [PATCH 048/168] added swagger response body example --- .../server/controller/DeviceController.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 260c1c91e0..e080574d36 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -21,8 +21,11 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -175,6 +178,15 @@ public class DeviceController extends BaseController { "If the user has the authority of 'Tenant Administrator', the server checks that the device is owned by the same tenant. " + "If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK", + examples = @io.swagger.annotations.Example( + value = { + @io.swagger.annotations.ExampleProperty( + mediaType="application/json", + value="{\"http\":\"curl -v -X POST http://localhost:8080/api/v1/0ySs4FTOn5WU15XLmal8/telemetry --header Content-Type:application/json --data {temperature:25}\"," + + "\"mqtt\":\"mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -i myClient1 -u myUsername1 -P myPassword -m {temperature:25}\"," + + "\"coap\":\"coap-client -m POST coap://localhost:5683/api/v1/0ySs4FTOn5WU15XLmal8/telemetry -t json -e {temperature:25}\"}")}))}) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device/{deviceId}/commands", method = RequestMethod.GET) @ResponseBody From cce56b9547f9c7cf89b8284b469aa5d5fded9ea7 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 10 Jul 2023 13:28:38 +0300 Subject: [PATCH 049/168] added test --- .../entitiy/SimpleTbEntityService.java | 2 +- .../resource/DefaultTbResourceService.java | 7 +--- .../controller/TbResourceControllerTest.java | 32 +++++++++++++++++++ .../dao/resource/BaseResourceService.java | 1 + .../server/dao/service/DataValidator.java | 4 +++ .../validator/ResourceDataValidator.java | 21 ++++++++++++ 6 files changed, 60 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java index 270662f263..daf0f346c8 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java @@ -26,6 +26,6 @@ public interface SimpleTbEntityService { T save(T entity, User user) throws Exception; - void delete(T entity, User user) throws ThingsboardException; + void delete(T entity, User user); } diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index c47adc42c1..064511abd9 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -151,15 +151,10 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements } @Override - public void delete(TbResource tbResource, User user) throws ThingsboardException { + public void delete(TbResource tbResource, User user) { TbResourceId resourceId = tbResource.getId(); TenantId tenantId = tbResource.getTenantId(); try { - List widgets = widgetTypeService.findWidgetTypesInfosByTenantIdAndResourceId(tenantId, resourceId); - if (!widgets.isEmpty()) { - List widgetNames = widgets.stream().map(BaseWidgetType::getName).collect(Collectors.toList()); - throw new ThingsboardException(String.format("Following widget types uses current resource: %s", widgetNames), ThingsboardErrorCode.GENERAL); - } resourceService.deleteResource(tenantId, resourceId); tbClusterService.onResourceDeleted(tbResource, null); notificationEntityService.logEntityAction(tenantId, resourceId, tbResource, ActionType.DELETED, user, resourceId.toString()); diff --git a/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java index b4735519a5..bb542e6cf1 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java @@ -38,6 +38,8 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DaoSqlTest; @@ -216,6 +218,36 @@ public class TbResourceControllerTest extends AbstractControllerTest { .andExpect(statusReason(containsString(msgErrorNoFound("Resource", resourceIdStr)))); } + @Test + public void testShoudNotDeleteTbResourceIfAssignedToWidgetType() throws Exception { + TbResource resource = new TbResource(); + resource.setResourceType(ResourceType.JKS); + resource.setTitle("My first resource"); + resource.setFileName(DEFAULT_FILE_NAME); + resource.setData(TEST_DATA); + + TbResource savedResource = save(resource); + + Mockito.reset(tbClusterService, auditLogService); + String resourceIdStr = savedResource.getId().getId().toString(); + + //create widget type + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTitle("My widgets bundle"); + WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); + + WidgetTypeDetails widgetType = new WidgetTypeDetails(); + widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetType.setName("Widget Type"); + widgetType.setDescriptor(JacksonUtil.fromString(String.format("{ \"resources\": [{\"url\":{\"entityType\":\"TB_RESOURCE\",\"id\":\"%s\"},\"isModule\":true}]}", savedResource.getId()), JsonNode.class)); + doPost("/api/widgetType", widgetType, WidgetTypeDetails.class); + + doDelete("/api/resource/" + resourceIdStr) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Following widget types uses current resource: [" + + widgetType .getName()+ "]"))); + } + @Test public void testFindTenantTbResources() throws Exception { diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java index bc4f47040b..7697217b6b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -109,6 +109,7 @@ public class BaseResourceService extends AbstractCachedEntityService> { return null; } + public void validateDelete(TenantId tenantId, EntityId entityId) { + } + protected boolean isSameData(D existentData, D actualData) { return actualData.getId() != null && existentData.getId().equals(actualData.getId()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java index c547f3c416..9939d887fa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java @@ -20,14 +20,21 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.widget.BaseWidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.resource.TbResourceDao; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.widget.WidgetTypeDao; + +import java.util.List; +import java.util.stream.Collectors; import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE; @@ -37,6 +44,9 @@ public class ResourceDataValidator extends DataValidator { @Autowired private TbResourceDao resourceDao; + @Autowired + private WidgetTypeDao widgetTypeDao; + @Autowired private TenantService tenantService; @@ -77,4 +87,15 @@ public class ResourceDataValidator extends DataValidator { } } } + + @Override + public void validateDelete(TenantId tenantId, EntityId resourceId) { + List widgets = widgetTypeDao.findWidgetTypesInfosByTenantIdAndResourceId(tenantId.getId(), + resourceId.getId()); + if (!widgets.isEmpty()) { + List widgetNames = widgets.stream().map(BaseWidgetType::getName).collect(Collectors.toList()); + throw new DataValidationException(String.format("Following widget types uses current resource: %s", widgetNames)); + } + } + } From 80dfb3c5294dd7a403c5412870994e80727620ee Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 10 Jul 2023 13:31:00 +0300 Subject: [PATCH 050/168] deleted redundant imports --- .../server/service/entitiy/SimpleTbEntityService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java index daf0f346c8..609a61b903 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java @@ -16,7 +16,6 @@ package org.thingsboard.server.service.entitiy; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.exception.ThingsboardException; public interface SimpleTbEntityService { From 0da9affe18e40a67d473c5243b902a252dde7c99 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 10 Jul 2023 16:48:13 +0300 Subject: [PATCH 051/168] fixed user phone display in entities table --- .../server/dao/sql/query/DefaultEntityQueryRepository.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index 2135e98eed..4cf6db0fc1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -69,7 +69,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { private static final Map entityTableMap = new HashMap<>(); private static final Map entityNameColumns = new HashMap<>(); private static final String SELECT_PHONE = " CASE WHEN entity.entity_type = 'TENANT' THEN (select phone from tenant where id = entity_id)" + - " WHEN entity.entity_type = 'CUSTOMER' THEN (select phone from customer where id = entity_id) END as phone"; + " WHEN entity.entity_type = 'CUSTOMER' THEN (select phone from customer where id = entity_id)" + + " WHEN entity.entity_type = 'USER' THEN (select phone from tb_user where id = entity_id) END as phone"; private static final String SELECT_ZIP = " CASE WHEN entity.entity_type = 'TENANT' THEN (select zip from tenant where id = entity_id)" + " WHEN entity.entity_type = 'CUSTOMER' THEN (select zip from customer where id = entity_id) END as zip"; private static final String SELECT_ADDRESS_2 = " CASE WHEN entity.entity_type = 'TENANT'" + From 4685139820d5b2e59cf0c2cf00591e32ae37e659 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 10 Jul 2023 17:38:22 +0300 Subject: [PATCH 052/168] UI: Add new unit models --- ui-ngx/src/app/shared/models/unit.models.ts | 2003 ++++++++++++++++- .../assets/locale/locale.constant-en_US.json | 413 +++- 2 files changed, 2405 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/shared/models/unit.models.ts b/ui-ngx/src/app/shared/models/unit.models.ts index 7ac0d4db57..e5ca8aa0b3 100644 --- a/ui-ngx/src/app/shared/models/unit.models.ts +++ b/ui-ngx/src/app/shared/models/unit.models.ts @@ -21,20 +21,1458 @@ export interface Unit { } export const units: Array = [ + { + name: 'unit.millimeter', + symbol: 'mm', + tags: ['level','height','distance','length','width','gap','depth','millimeter','millimeters','rainfall','precipitation', + 'displacement','position','movement','transition','mm'] + }, + { + name: 'unit.centimeter', + symbol: 'cm', + tags: ['level','height','distance','length','width','gap','depth','centimeter','centimeters','rainfall','precipitation', + 'displacement','position','movement','transition','cm'] + }, + { + name: 'unit.angstrom', + symbol: 'Å', + tags: ['level','height','distance','length','width','gap','depth','atomic scale','atomic distance','nanoscale', + 'angstrom','angstroms','Å'] + }, + { + name: 'unit.nanometer', + symbol: 'nm', + tags: ['level','height','distance','length','width','gap','depth','nanoscale','atomic scale','molecular scale', + 'nanometer','nanometers','nm'] + }, + { + name: 'unit.micrometer', + symbol: 'µm', + tags: ['level','height','distance','length','width','gap','depth','microns','micrometer','micrometers','µm'] + }, + { + name: 'unit.meter', + symbol: 'm', + tags: ['level','height','distance','length','width','gap','depth','meter','meters','m'] + }, + { + name: 'unit.kilometer', + symbol: 'km', + tags: ['distance','height','length','width','gap','depth','kilometer','kilometers','km'] + }, + { + name: 'unit.inch', + symbol: 'in', + tags: ['level','height','distance','length','width','gap','depth','inch','inches','in'] + }, + { + name: 'unit.foot', + symbol: 'ft', + tags: ['level','height','distance','length','width','gap','depth','foot','feet','ft'] + }, + { + name: 'unit.yard', + symbol: 'yd', + tags: ['level','height','distance','length','width','gap','depth','yard','yards','yd'] + }, + { + name: 'unit.mile', + symbol: 'mi', + tags: ['level','height','distance','length','width','gap','depth','mile','miles','mi'] + }, + { + name: 'unit.nautical-mile', + symbol: 'nm', + tags: ['level','height','distance','length','width','gap','depth','nautical mile','nm'] + }, + { + name: 'unit.astronomical-unit', + symbol: 'AU', + tags: ['distance','celestial bodies','solar system','AU'] + }, + { + name: 'unit.reciprocal-metre', + symbol: 'm⁻¹', + tags: ['wavenumber','wave density','wave frequency','m⁻¹'] + }, + { + name: 'unit.meter-per-meter', + symbol: 'm/m', + tags: ['ratio of length to length','meter per meter','m/m'] + }, + { + name: 'unit.steradian', + symbol: 'sr', + tags: ['solid angle','spatial extent','steradian','sr'] + }, + { + name: 'unit.thou', + symbol: 'thou', + tags: ['length','measurement','thou'] + }, + { + name: 'unit.barleycorn', + symbol: 'barleycorn', + tags: ['length','shoe size','barleycorn'] + }, + { + name: 'unit.hand', + symbol: 'hand', + tags: ['length','horse measurement','hand'] + }, + { + name: 'unit.chain', + symbol: 'ch', + tags: ['length','land surveying','ch'] + }, + { + name: 'unit.furlong', + symbol: 'fur', + tags: ['length','land surveying','fur'] + }, + { + name: 'unit.league', + symbol: 'league', + tags: ['length','historical measurement','league'] + }, + { + name: 'unit.fathom', + symbol: 'fathom', + tags: ['depth','nautical measurement','fathom'] + }, + { + name: 'unit.cable', + symbol: 'cable', + tags: ['distance','nautical measurement','cable'] + }, + { + name: 'unit.link', + symbol: 'link', + tags: ['length','land surveying','link'] + }, + { + name: 'unit.rod', + symbol: 'rod', + tags: ['length','land surveying','rod'] + }, + { + name: 'unit.nanogram', + symbol: 'ng', + tags: ['mass','weight','heaviness','load','nanogram','nanograms','ng'] + }, + { + name: 'unit.microgram', + symbol: 'μg', + tags: ['mass','weight','heaviness','load','μg','microgram'] + }, + { + name: 'unit.milligram', + symbol: 'mg', + tags: ['mass','weight','heaviness','load','milligram','miligrams','mg'] + }, + { + name: 'unit.gram', + symbol: 'g', + tags: ['mass','weight','heaviness','load','gram','grams','g'] + }, + { + name: 'unit.kilogram', + symbol: 'kg', + tags: ['mass','weight','heaviness','load','kilogram','kilograms','kg'] + }, + { + name: 'unit.tonne', + symbol: 't', + tags: ['mass','weight','heaviness','load','tonne','tons','t'] + }, + { + name: 'unit.ounce', + symbol: 'oz', + tags: ['mass','weight','heaviness','load','ounce','ounces','oz'] + }, + { + name: 'unit.pound', + symbol: 'lb', + tags: ['mass','weight','heaviness','load','pound','pounds','lb'] + }, + { + name: 'unit.stone', + symbol: 'st', + tags: ['mass','weight','heaviness','load','stone','stones','st'] + }, + { + name: 'unit.hundredweight-count', + symbol: 'cwt', + tags: ['mass','weight','heaviness','load','hundredweight count','cwt'] + }, + { + name: 'unit.short-tons', + symbol: 'short tons', + tags: ['mass','weight','heaviness','load','short ton','short tons'] + }, + { + name: 'unit.dalton', + symbol: 'Da', + tags: ['atomic mass unit','AMU','unified atomic mass unit','dalton','Da'] + }, + { + name: 'unit.grain', + symbol: 'gr', + tags: ['mass','measurement','grain','gr'] + }, + { + name: 'unit.drachm', + symbol: 'dr', + tags: ['mass','measurement','drachm','dr'] + }, + { + name: 'unit.quarter', + symbol: 'qr', + tags: ['mass','measurement','quarter','qr'] + }, + { + name: 'unit.slug', + symbol: 'slug', + tags: ['mass','measurement','slug'] + }, + { + name: 'unit.carat', + symbol: 'ct', + tags: ['gemstone','pearl','jewelry','carat','ct'] + }, + { + name: 'unit.cubic-millimeter', + symbol: 'mm³', + tags: ['volume','capacity','extent','cubic millimeter','mm³'] + }, + { + name: 'unit.cubic-centimeter', + symbol: 'cm³', + tags: ['volume','capacity','extent','cubic centimeter','cubic centimeters','cm³'] + }, + { + name: 'unit.cubic-meter', + symbol: 'm³', + tags: ['volume','capacity','extent','cubic meter','cubic meters','m³'] + }, + { + name: 'unit.cubic-kilometer', + symbol: 'km³', + tags: ['volume','capacity','extent','cubic kilometer','cubic kilometers','km³'] + }, + { + name: 'unit.microliter', + symbol: 'µL', + tags: ['volume','liquid measurement','microliter','µL'] + }, + { + name: 'unit.milliliter', + symbol: 'mL', + tags: ['volume','capacity','extent','milliliter','milliliters','mL'] + }, + { + name: 'unit.liter', + symbol: 'l', + tags: ['volume','capacity','extent','liter','liters','l'] + }, + { + name: 'unit.hectoliter', + symbol: 'hl', + tags: ['volume','capacity','extent','hectoliter','hectoliters','hl'] + }, + { + name: 'unit.cubic-inch', + symbol: 'in³', + tags: ['volume','capacity','extent','cubic inch','cubic inches','in³'] + }, + { + name: 'unit.cubic-foot', + symbol: 'ft³', + tags: ['volume','capacity','extent','cubic foot','cubic feet','ft³'] + }, + { + name: 'unit.cubic-yard', + symbol: 'yd³', + tags: ['volume','capacity','extent','cubic yard','cubic yards','yd³'] + }, + { + name: 'unit.fluid-ounce', + symbol: 'fl-oz', + tags: ['volume','capacity','extent','fluid ounce','fluid ounces','fl-oz'] + }, + { + name: 'unit.pint', + symbol: 'pt', + tags: ['volume','capacity','extent','pint','pints','pt'] + }, + { + name: 'unit.quart', + symbol: 'qt', + tags: ['volume','capacity','extent','quart','quarts','qt'] + }, + { + name: 'unit.gallon', + symbol: 'gal', + tags: ['volume','capacity','extent','gallon','gallons','gal'] + }, + { + name: 'unit.oil-barrels', + symbol: 'bbl', + tags: ['volume','capacity','extent','oil barrel','oil barrels','bbl'] + }, + { + name: 'unit.cubic-meter-per-kilogram', + symbol: 'm³/kg', + tags: ['specific volume','volume per unit mass','cubic meter per kilogram','m³/kg'] + }, + { + name: 'unit.gill', + symbol: 'gi', + tags: ['volume','liquid measurement','gi'] + }, + { + name: 'unit.hogshead', + symbol: 'hhd', + tags: ['volume','liquid measurement','hhd'] + }, + { + name: 'unit.teaspoon', + symbol: 'tsp', + tags: ['volume','cooking measurement','tsp'] + }, + { + name: 'unit.tablespoon', + symbol: 'tbsp', + tags: ['volume','cooking measurement','tbsp'] + }, + { + name: 'unit.cup', + symbol: 'cup', + tags: ['volume','cooking measurement','cup'] + }, { name: 'unit.celsius', symbol: '°C', - tags: ['temperature'] + tags: ['temperature','heat','cold','warmth','degrees','celsius','shipment condition','°C'] + }, + { + name: 'unit.kelvin', + symbol: 'K', + tags: ['temperature','heat','cold','warmth','degrees','kelvin','K','color quality','white balance','color temperature'] + }, + { + name: 'unit.rankine', + symbol: '°R', + tags: ['temperature','heat','cold','warmth','Rankine','°R'] + }, + { + name: 'unit.fahrenheit', + symbol: '°F', + tags: ['temperature','heat','cold','warmth','degrees','fahrenheit','°F'] + }, + { + name: 'unit.meter-per-second', + symbol: 'm/s', + tags: ['speed','velocity','pace','meter per second','m/s','peak','peak to peak','root mean square (RMS)', + 'vibration','wind speed','weather'] + }, + { + name: 'unit.kilometer-per-hour', + symbol: 'km/h', + tags: ['speed','velocity','pace','kilometer per hour','km/h'] + }, + { + name: 'unit.foot-per-second', + symbol: 'ft/s', + tags: ['speed','velocity','pace','foot per second','ft/s'] + }, + { + name: 'unit.mile-per-hour', + symbol: 'mph', + tags: ['speed','velocity','pace','mile per hour','mph'] + }, + { + name: 'unit.knot', + symbol: 'kt', + tags: ['speed','velocity','pace','knot','knots','kt'] + }, + { + name: 'unit.millimeters-per-minute', + symbol: 'mm/min', + tags: ['feed rate','cutting feed rate','millimeters per minute','mm/min'] + }, + { + name: 'unit.kilometer-per-hour-squared', + symbol: 'km/h²', + tags: ['acceleration','rate of change of velocity','kilometer per hour squared','km/h²'] + }, + { + name: 'unit.foot-per-second-squared', + symbol: 'ft/s²', + tags: ['acceleration','rate of change of velocity','foot per second squared','ft/s²'] + }, + { + name: 'unit.pascal', + symbol: 'Pa', + tags: ['pressure','force','compression','tension','pascal','pascals','Pa','atmospheric pressure','air pressure', + 'weather','altitude','flight'] + }, + { + name: 'unit.kilopascal', + symbol: 'kPa', + tags: ['pressure','force','compression','tension','kilopascal','kilopascals','kPa'] + }, + { + name: 'unit.megapascal', + symbol: 'MPa', + tags: ['pressure','force','compression','tension','megapascal','megapascals','MPa'] + }, + { + name: 'unit.gigapascal', + symbol: 'GPa', + tags: ['pressure','force','compression','tension','gigapascal','gigapascals','GPa'] + }, + { + name: 'unit.millibar', + symbol: 'mbar', + tags: ['pressure','force','compression','tension','millibar','millibars','mbar'] + }, + { + name: 'unit.bar', + symbol: 'bar', + tags: ['pressure','force','compression','tension','bar','bars'] + }, + { + name: 'unit.kilobar', + symbol: 'kbar', + tags: ['pressure','force','compression','tension','kilobar','kilobars','kbar'] + }, + { + name: 'unit.newton', + symbol: 'N', + tags: ['force','pressure','newton','newtons','N','push','pull','weight','gravity','N'] + }, + { + name: 'unit.newton-meter', + symbol: 'Nm', + tags: ['torque','rotational force','newton meter','Nm'] + }, + { + name: 'unit.foot-pounds', + symbol: 'ft·lbf', + tags: ['torque','rotational force','foot-pound','foot-pounds','ft·lbf'] + }, + { + name: 'unit.inch-pounds', + symbol: 'in·lbf', + tags: ['torque','rotational force','inch-pounds','inch-pound','in·lbf'] + }, + { + name: 'unit.newton-per-meter', + symbol: 'N/m', + tags: ['linear density','force per unit length','newton per meter','N/m'] + }, + { + name: 'unit.atmospheres', + symbol: 'atm', + tags: ['pressure','force','compression','tension','atmosphere','atmospheres','atmospheric pressure','atm'] + }, + { + name: 'unit.pounds-per-square-inch', + symbol: 'psi', + tags: ['pressure','force','compression','tension','pounds per square inch','psi'] + }, + { + name: 'unit.torr', + symbol: 'Torr', + tags: ['pressure','force','compression','tension','vacuum pressure','torr'] + }, + { + name: 'unit.inches-of-mercury', + symbol: 'inHg', + tags: ['pressure','force','compression','tension','vacuum pressure','inHg','atmospheric pressure','barometric pressure'] + }, + { + name: 'unit.pascal-per-square-meter', + symbol: 'Pa/m²', + tags: ['pressure','stress','mechanical strength','pascal per square meter','Pa/m²'] + }, + { + name: 'unit.pound-per-square-inch', + symbol: 'psi/in²', + tags: ['pressure','stress','mechanical strength','pound per square inch','psi/in²'] + }, + { + name: 'unit.newton-per-square-meter', + symbol: 'N/m²', + tags: ['pressure','stress','mechanical strength','newton per square meter','N/m²'] + }, + { + name: 'unit.kilogram-force-per-square-meter', + symbol: 'kgf/m²', + tags: ['pressure','stress','mechanical strength','kilogram-force per square meter','kgf/m²'] + }, + { + name: 'unit.pascal-per-square-centimeter', + symbol: 'Pa/cm²', + tags: ['pressure','stress','mechanical strength','pascal per square centimeter','Pa/cm²'] + }, + { + name: 'unit.ton-force-per-square-inch', + symbol: 'tonf/in²', + tags: ['pressure','stress','mechanical strength','ton-force per square inch','tonf/in²'] + }, + { + name: 'unit.kilonewton-per-square-meter', + symbol: 'kN/m²', + tags: ['stress','pressure','mechanical strength','kilonewton per square meter','kN/m²'] + }, + { + name: 'unit.newton-per-square-millimeter', + symbol: 'N/mm²', + tags: ['stress','pressure','mechanical strength','newton per square millimeter','N/mm²'] + }, + { + name: 'unit.microjoule', + symbol: 'μJ', + tags: ['energy','microjoule','microjoules','μJ'] + }, + { + name: 'unit.millijoule', + symbol: 'mJ', + tags: ['energy','millijoule','millijoules','mJ'] + }, + { + name: 'unit.joule', + symbol: 'J', + tags: ['joule','joules','energy','work done','heat','electricity','mechanical work'] + }, + { + name: 'unit.kilojoule', + symbol: 'kJ', + tags: ['energy','kilojoule','kilojoules','kJ'] + }, + { + name: 'unit.megajoule', + symbol: 'MJ', + tags: ['energy','megajoule','megajoules','MJ'] + }, + { + name: 'unit.gigajoule', + symbol: 'GJ', + tags: ['energy','gigajoule','gigajoules','GJ'] + }, + { + name: 'unit.watt-hour', + symbol: 'Wh', + tags: ['energy','watt-hour','watt-hours','energy usage','power consumption','energy consumption','electricity usage'] + }, + { + name: 'unit.kilowatt-hour', + symbol: 'kWh', + tags: ['energy','kilowatt-hour','kilowatt-hours','energy usage','power consumption','energy consumption','electricity usage'] + }, + { + name: 'unit.electron-volts', + symbol: 'eV', + tags: ['energy','subatomic particles','radiation'] + }, + { + name: 'unit.joules-per-coulomb', + symbol: 'J/C', + tags: ['electrical potential energy','voltage','joules per coulomb','J/C'] + }, + { + name: 'unit.british-thermal-unit', + symbol: 'BTU', + tags: ['energy','heat','work done','british thermal unit','british thermal units','BTU'] + }, + { + name: 'unit.foot-pound', + symbol: 'ft·lb', + tags: ['energy','foot-pound','foot-pounds','ft·lb','ft⋅lbf'] + }, + { + name: 'unit.calorie', + symbol: 'Cal', + tags: ['energy','food energy','Calorie','Calories','Cal'] + }, + { + name: 'unit.small-calorie', + symbol: 'cal', + tags: ['energy','small calorie','calories','cal'] + }, + { + name: 'unit.kilocalorie', + symbol: 'kcal', + tags: ['energy','small calorie','kilocalories','kcal'] + }, + { + name: 'unit.joule-per-kelvin', + symbol: 'J/K', + tags: ['specific heat capacity','heat capacity per unit temperature','joule per kelvin','J/K'] + }, + { + name: 'unit.joule-per-kilogram-kelvin', + symbol: 'J/(kg·K)', + tags: ['specific heat capacity','heat capacity per unit mass and temperature','joule per kilogram-kelvin','J/(kg·K)'] + }, + { + name: 'unit.joule-per-kilogram', + symbol: 'J/kg', + tags: ['specific energy','specific energy capacity','joule per kilogram','J/kg'] + }, + { + name: 'unit.watt-per-meter-kelvin', + symbol: 'W/(m·K)', + tags: ['thermal conductivity','watt per meter-kelvin','W/(m·K)'] + }, + { + name: 'unit.joule-per-cubic-meter', + symbol: 'J/m³', + tags: ['energy density','joule per cubic meter','J/m³'] + }, + { + name: 'unit.therm', + symbol: 'thm', + tags: ['energy','natural gas consumption','BTU','therm','thm'] + }, + { + name: 'unit.electric-dipole-moment', + symbol: 'C·m', + tags: ['electric dipole','dipole moment','coulomb meter','C·m'] + }, + { + name: 'unit.magnetic-dipole-moment', + symbol: 'A·m²', + tags: ['magnetic dipole','dipole moment','ampere square meter','A·m²'] + }, + { + name: 'unit.debye', + symbol: 'D', + tags: ['polarization','electric dipole moment','debye','D'] + }, + { + name: 'unit.coulomb-per-square-meter-per-volt', + symbol: 'C·m²/V', + tags: ['polarization','electric field','coulomb per square meter per volt','C·m²/V'] + }, + { + name: 'unit.milliwatt', + symbol: 'mW', + tags: ['power','horsepower','performance','milliwatt','milliwatts','electricity','mW'] + }, + { + name: 'unit.microwatt', + symbol: 'μW', + tags: ['power','horsepower','performance','microwatt','microwatts','electricity','μW'] + }, + { + name: 'unit.watt', + symbol: 'W', + tags: ['power','horsepower','performance','watt','watts','electricity','W'] + }, + { + name: 'unit.kilowatt', + symbol: 'kW', + tags: ['power','horsepower','performance','kilowatt','kilowatts','electricity','kW'] + }, + { + name: 'unit.megawatt', + symbol: 'MW', + tags: ['power','horsepower','performance','megawatt','megawatts','electricity','MW'] + }, + { + name: 'unit.gigawatt', + symbol: 'GW', + tags: ['power','horsepower','performance','gigawatt','gigawatts','electricity','GW'] + }, + { + name: 'unit.metric-horsepower', + symbol: 'PS', + tags: ['power','performance','metric horsepower','PS'] + }, + { + name: 'unit.milliwatt-per-square-centimeter', + symbol: 'mW/cm²', + tags: ['power density','radiation intensity','sunlight intensity','signal power','intensity', + 'milliwatts per square centimeter','UV Intensity','mW/cm²'] + }, + { + name: 'unit.watt-per-square-centimeter', + symbol: 'W/cm²', + tags: ['power density','intensity of power','watts per square centimeter','W/cm²'] + }, + { + name: 'unit.kilowatt-per-square-centimeter', + symbol: 'kW/cm²', + tags: ['power density','intensity of power','kilowatts per square centimeter','kW/cm²'] + }, + { + name: 'unit.milliwatt-per-square-meter', + symbol: 'mW/m²', + tags: ['power density','intensity of power','milliwatts per square meter','mW/m²'] + }, + { + name: 'unit.watt-per-square-meter', + symbol: 'W/m²', + tags: ['power density','intensity of power','watts per square meter','W/m²'] + }, + { + name: 'unit.kilowatt-per-square-meter', + symbol: 'kW/m²', + tags: ['power density','intensity of power','kilowatts per square meter','kW/m²'] + }, + { + name: 'unit.watt-per-square-inch', + symbol: 'W/in²', + tags: ['power density','intensity of power','watts per square inch','W/in²'] + }, + { + name: 'unit.kilowatt-per-square-inch', + symbol: 'kW/in²', + tags: ['power density','intensity of power','kilowatts per square inch','kW/in²'] + }, + { + name: 'unit.horsepower', + symbol: 'hp', + tags: ['power','horsepower','performance','electricity','horsepowers','hp'] + }, + { + name: 'unit.btu-per-hour', + symbol: 'BTU/h', + tags: ['power','heat transfer','thermal energy','HVAC','BTU/h'] + }, + { + name: 'unit.coulomb', + symbol: 'C', + tags: ['charge','electricity','electrostatics','Coulomb','C'] + }, + { + name: 'unit.millicoulomb', + symbol: 'mC', + tags: ['charge','electricity','electrostatics','millicoulombs','mC'] + }, + { + name: 'unit.microcoulomb', + symbol: 'µC', + tags: ['charge','electricity','electrostatics','microcoulomb','µC'] + }, + { + name: 'unit.picocoulomb', + symbol: 'pC', + tags: ['charge','electricity','electrostatics','picocoulomb','pC'] + }, + { + name: 'unit.coulomb-per-meter', + symbol: 'C/m', + tags: ['electric displacement field per length','coulomb per meter','C/m'] + }, + { + name: 'unit.coulomb-per-cubic-meter', + symbol: 'C/m³', + tags: ['electric charge density','coulomb per cubic meter','C/m³'] + }, + { + name: 'unit.coulomb-per-square-meter', + symbol: 'C/m²', + tags: ['electric surface charge density','coulomb per square meter','C/m²'] + }, + { + name: 'unit.square-millimeter', + symbol: 'mm²', + tags: ['area','lot','zone','space','region','square millimeter','square millimeters','mm²','sq-mm'] + }, + { + name: 'unit.square-centimeter', + symbol: 'cm²', + tags: ['area','lot','zone','space','region','square centimeter','square centimeters','cm²','sq-cm'] + }, + { + name: 'unit.square-meter', + symbol: 'm²', + tags: ['area','lot','zone','space','region','square meter','square meters','m²','sq-m'] + }, + { + name: 'unit.hectare', + symbol: 'ha', + tags: ['area','lot','zone','space','region','hectare','hectares','ha'] + }, + { + name: 'unit.square-kilometer', + symbol: 'km²', + tags: ['area','lot','zone','space','region','square kilometer','square kilometers','km²','sq-km'] + }, + { + name: 'unit.square-inch', + symbol: 'in²', + tags: ['area','lot','zone','space','region','square inch','square inches','in²','sq-in'] + }, + { + name: 'unit.square-foot', + symbol: 'ft²', + tags: ['area','lot','zone','space','region','square foot','square feet','ft²','sq-ft'] + }, + { + name: 'unit.square-yard', + symbol: 'yd²', + tags: ['area','lot','zone','space','region','square yard','square yards','yd²','sq-yd'] + }, + { + name: 'unit.acre', + symbol: 'a', + tags: ['area','lot','zone','space','region','acre','acres','a'] + }, + { + name: 'unit.square-mile', + symbol: 'ml²', + tags: ['area','lot','zone','space','region','square mile','square miles','ml²','sq-mi'] + }, + { + name: 'unit.are', + symbol: 'are', + tags: ['area','land measurement','are'] + }, + { + name: 'unit.barn', + symbol: 'barn', + tags: ['cross-sectional area','particle physics','nuclear physics','barn'] + }, + { + name: 'unit.circular-inch', + symbol: 'circin', + tags: ['area','circular measurement','circular inch','circin'] + }, + { + name: 'unit.milliampere-hour', + symbol: 'mAh', + tags: ['electric current','current flow','electric charge','current capacity','flow of electricity', + 'electrical flow','milliampere-hour','milliampere-hours','mAh'] + }, + { + name: 'unit.ampere-hours', + symbol: 'Ah', + tags: ['electric current','current flow','electric charge','current capacity','flow of electricity', + 'electrical flow','ampere','ampere-hours','Ah'] + }, + { + name: 'unit.kiloampere-hours', + symbol: 'kAh', + tags: ['electric current','current flow','electric charge','current capacity','flow of electricity','electrical flow', + 'kiloampere-hours','kiloampere-hour','kAh'] + }, + { + name: 'unit.nanoampere', + symbol: 'nA', + tags: ['current','amperes','nanoampere','nA'] + }, + { + name: 'unit.picoampere', + symbol: 'pA', + tags: ['current','amperes','picoampere','pA'] + }, + { + name: 'unit.microampere', + symbol: 'μA', + tags: ['electric current','microampere','microamperes','μA'] + }, + { + name: 'unit.milliampere', + symbol: 'mA', + tags: ['electric current','milliampere','milliamperes','mA'] + }, + { + name: 'unit.ampere', + symbol: 'A', + tags: ['electric current','current flow','flow of electricity','electrical flow','ampere','amperes','amperage','A'] + }, + { + name: 'unit.kiloamperes', + symbol: 'kA', + tags: ['electric current','current flow','kiloamperes','kA'] + }, + { + name: 'unit.microampere-per-square-centimeter', + symbol: 'µA/cm²', + tags: ['Current density','microampere per square centimeter','µA/cm²'] + }, + { + name: 'unit.ampere-per-square-meter', + symbol: 'A/m²', + tags: ['current density','current per unit area','ampere per square meter','A/m²'] + }, + { + name: 'unit.ampere-per-meter', + symbol: 'A/m', + tags: ['magnetic field strength','magnetic field intensity','ampere per meter','A/m'] + }, + { + name: 'unit.oersted', + symbol: 'Oe', + tags: ['magnetic field','oersted','Oe'] + }, + { + name: 'unit.bohr-magneton', + symbol: 'μB', + tags: ['atomic physics','magnetic moment','bohr magneton','μB'] + }, + { + name: 'unit.ampere-meter-squared', + symbol: 'A·m²', + tags: ['magnetic moment','dipole moment','ampere-meter squared','A·m²'] + }, + { + name: 'unit.ampere-meter', + symbol: 'A·m', + tags: ['magnetic field','current loop','ampere-meter','A·m'] + }, + { + name: 'unit.nanovolt', + symbol: 'nV', + tags: ['voltage','volts','nanovolt','nV'] + }, + { + name: 'unit.picovolt', + symbol: 'pV', + tags: ['voltage','volts','picovolt','pV'] + }, + { + name: 'unit.millivolts', + symbol: 'mV', + tags: ['electric potential','electric tension','voltage','millivolt','millivolts','mV'] + }, + { + name: 'unit.microvolts', + symbol: 'μV', + tags: ['electric potential','electric tension','voltage','microvolt','microvolts','μV'] + }, + { + name: 'unit.volt', + symbol: 'V', + tags: ['electric potential','electric tension','voltage','volt','volts','V','power source','battery','battery level'] + }, + { + name: 'unit.kilovolts', + symbol: 'kV', + tags: ['electric potential','electric tension','voltage','kilovolt','kilovolts','kV'] + }, + { + name: 'unit.dbmV', + symbol: 'dBmV', + tags: ['decibels millivolt','voltage level','signal','dBmV'] + }, + { + name: 'unit.volt-meter', + symbol: 'V·m', + tags: ['electric flux','volt-meter','V·m'] + }, + { + name: 'unit.kilovolt-meter', + symbol: 'kV·m', + tags: ['electric flux','kilovolt-meter','kV·m'] + }, + { + name: 'unit.megavolt-meter', + symbol: 'MV·m', + tags: ['electric flux','megavolt-meter','MV·m'] + }, + { + name: 'unit.microvolt-meter', + symbol: 'µV·m', + tags: ['electric flux','microvolt-meter','µV·m'] + }, + { + name: 'unit.millivolt-meter', + symbol: 'mV·m', + tags: ['electric flux','millivolt-meter','mV·m'] + }, + { + name: 'unit.nanovolt-meter', + symbol: 'nV·m', + tags: ['electric flux','nanovolt-meter','nV·m'] + }, + { + name: 'unit.ohm', + symbol: 'Ω', + tags: ['electrical resistance','resistance','impedance','ohm'] + }, + { + name: 'unit.microohm', + symbol: 'μΩ', + tags: ['electrical resistance','resistance','microohm','μΩ'] + }, + { + name: 'unit.milliohm', + symbol: 'mΩ', + tags: ['electrical resistance','resistance','milliohm','mΩ'] + }, + { + name: 'unit.kilohm', + symbol: 'kΩ', + tags: ['electrical resistance','resistance','kilohm','kΩ'] + }, + { + name: 'unit.megohm', + symbol: 'MΩ', + tags: ['electrical resistance','resistance','megohm','MΩ'] + }, + { + name: 'unit.gigohm', + symbol: 'GΩ', + tags: ['electrical resistance','resistance','gigohm','GΩ'] + }, + { + name: 'unit.hertz', + symbol: 'Hz', + tags: ['frequency','cycles per second','hertz','Hz'] + }, + { + name: 'unit.kilohertz', + symbol: 'kHz', + tags: ['frequency','cycles per second','kilohertz','kHz'] + }, + { + name: 'unit.megahertz', + symbol: 'MHz', + tags: ['frequency','cycles per second','megahertz','MHz'] + }, + { + name: 'unit.gigahertz', + symbol: 'GHz', + tags: ['frequency','cycles per second','gigahertz','GHz'] + }, + { + name: 'unit.rpm', + symbol: 'RPM', + tags: ['speed','velocity','cycle','engine','Revolutions Per Minute','RPM','angular velocity','rotation speed'] + }, + { + name: 'unit.candela-per-square-meter', + symbol: 'cd/m²', + tags: ['brightness','light level','Luminance','Candela per square meter','cd/m²'] + }, + { + name: 'unit.candela', + symbol: 'cd', + tags: ['light intensity','candle power','luminous intensity','Candela','cd'] + }, + { + name: 'unit.lumen', + symbol: 'lm', + tags: ['total light output','light power','luminous flux','Lumen','lm'] + }, + { + name: 'unit.lux', + symbol: 'lx', + tags: ['illumination','light level on a surface','illuminance','Lux','lx'] + }, + { + name: 'unit.foot-candle', + symbol: 'fc', + tags: ['illuminance','light level','foot-candle','fc'] + }, + { + name: 'unit.lumen-per-square-meter', + symbol: 'lm/m²', + tags: ['illuminance','light level','lumen per square meter','lm/m²'] + }, + { + name: 'unit.lux-second', + symbol: 'lx·s', + tags: ['light exposure','illumination time','light dosage','Lux second','lx·s'] + }, + { + name: 'unit.lumen-second', + symbol: 'lm·s', + tags: ['total light energy','luminous energy','Lumen second','lm·s'] + }, + { + name: 'unit.lumens-per-watt', + symbol: 'lm/W', + tags: ['lighting efficiency','light output per energy','luminous efficacy','Lumens per watt','lm/W'] + }, + { + name: 'unit.absorbance', + symbol: 'AU', + tags: ['optical density','light absorption','absorbance','AU'] + }, + { + name: 'unit.mole', + symbol: 'mol', + tags: ['amount of substance','substance quantity','mole','moles','mol'] + }, + { + name: 'unit.nanomole', + symbol: 'nmol', + tags: ['amount of substance','substance quantity','concentration','nanomole','nmol'] + }, + { + name: 'unit.micromole', + symbol: 'μmol', + tags: ['amount of substance','substance quantity','micromole','μmol'] + }, + { + name: 'unit.millimole', + symbol: 'mmol', + tags: ['amount of substance','substance quantity','millimole','mmol'] + }, + { + name: 'unit.kilomole', + symbol: 'kmol', + tags: ['amount of substance','substance quantity','kilomole','kmol'] + }, + { + name: 'unit.mole-per-cubic-meter', + symbol: 'mol/m³', + tags: ['concentration','amount of substance','mole per cubic meter','mol/m³'] + }, + { + name: 'unit.battery', + symbol: '%', + tags: ['power source','state of charge (SoC)','battery','battery level','level','humidity','moisture', + 'relative humidity','water content','soil moisture','irrigation','water in soil','soil water content','VWC', + 'Volumetric Water Content','Total Harmonic Distortion','THD','power quality','UV Transmittance','%'] + }, + { + name: 'unit.rssi', + symbol: 'rssi', + tags: ['signal strength','signal level','received signal strength indicator','rssi','dBm'] + }, + { + name: 'unit.ppm', + symbol: 'ppm', + tags: ['carbon dioxide','co²','carbon monoxide','co','aqi','air quality','total volatile organic compounds','tvoc','ppm'] + }, + { + name: 'unit.ppb', + symbol: 'ppb', + tags: ['ozone','o³','nitrogen dioxide','no²','sulfur dioxide','so²','aqi','air quality','tvoc','ppb'] + }, + { + name: 'unit.micrograms-per-cubic-meter', + symbol: 'µg/m³', + tags: ['coarse particulate matter','pm10','fine particulate matter','pm2.5','aqi','air quality', + 'total volatile organic compounds','tvoc','micrograms per cubic meter','µg/m³'] + }, + { + name: 'unit.aqi', + symbol: 'aqi', + tags: ['AQI','air quality index'] + }, + { + name: 'unit.gram-per-cubic-meter', + symbol: 'g/m³', + tags: ['humidity','moisture','absolute humidity','g/m³'] + }, + { + name: 'unit.gram-per-kilogram', + symbol: 'g/kg', + tags: ['humidity','moisture','specific humidity','g/kg'] + }, + { + name: 'unit.millimeters-per-second', + symbol: 'mm/s', + tags: ['velocity','speed','rate of motion','peak','peak to peak','root mean square (RMS)','vibration','mm/s'] + }, + { + name: 'unit.neper', + symbol: 'Np', + tags: ['logarithmic unit','ratio','gain','loss','attenuation','neper','Np'] + }, + { + name: 'unit.bel', + symbol: 'B', + tags: ['logarithmic unit','power ratio','intensity ratio','bel','B'] + }, + { + name: 'unit.decibel', + symbol: 'dB', + tags: ['noise level','sound level','volume','acoustics','decibel','dB'] + }, + { + name: 'unit.meters-per-second-squared', + symbol: 'm/s²', + tags: ['peak','peak to peak','root mean square (RMS)','vibration','meters per second squared','m/s²'] + }, + { + name: 'unit.becquerel', + symbol: 'Bq', + tags: ['radioactivity','radiation','becquerel','Bq'] + }, + { + name: 'unit.curie', + symbol: 'Ci', + tags: ['radioactivity','radiation','curie','Ci'] + }, + { + name: 'unit.gray', + symbol: 'Gy', + tags: ['radiation dose','gray','Gy'] + }, + { + name: 'unit.sievert', + symbol: 'Sv', + tags: ['radiation dose','sievert','radiation dose equivalent2','Sv'] + }, + { + name: 'unit.roentgen', + symbol: 'R', + tags: ['radiation exposure','roentgen','R'] + }, + { + name: 'unit.cps', + symbol: 'cps', + tags: ['radiation detection','counts per second','cps'] + }, + { + name: 'unit.rad', + symbol: 'Rad', + tags: ['radiation dose','rad'] + }, + { + name: 'unit.rem', + symbol: 'Rem', + tags: ['radiation dose equivalent','rem'] + }, + { + name: 'unit.dps', + symbol: 'dps', + tags: ['radioactive decay','radioactivity','disintegrations per second','dps'] + }, + { + name: 'unit.rutherford', + symbol: 'Rd', + tags: ['radioactive decay','radioactivity','rutherford','Rd'] + }, + { + name: 'unit.coulombs-per-kilogram', + symbol: 'C/kg', + tags: ['radiation exposure','dose','coulombs per kilogram','electric charge-to-mass ratio','C/kg'] + }, + { + name: 'unit.becquerels-per-cubic-meter', + symbol: 'Bq/m³', + tags: ['radioactivity','radiation','becquerels per cubic meter','Bq/m³'] + }, + { + name: 'unit.curies-per-liter', + symbol: 'Ci/L', + tags: ['radioactivity','radiation','curies per liter','Ci/L'] + }, + { + name: 'unit.becquerels-per-second', + symbol: 'Bq/s', + tags: ['radioactive decay rate','becquerels per second','Bq/s'] + }, + { + name: 'unit.curies-per-second', + symbol: 'Ci/s', + tags: ['radioactive decay rate','curies per second','Ci/s'] + }, + { + name: 'unit.gy-per-second', + symbol: 'Gy/s', + tags: ['absorbed dose rate','radiation dose rate','gray per second','Gy/s'] + }, + { + name: 'unit.watt-per-steradian', + symbol: 'W/sr', + tags: ['radiant intensity','power per unit solid angle','watt per steradian','W/sr'] + }, + { + name: 'unit.watt-per-square-metre-steradian', + symbol: 'W/(m²·sr)', + tags: ['radiance','radiant flux density','watt per square metre-steradian','W/(m²·sr)'] + }, + { + name: 'unit.ph-level', + symbol: 'pH', + tags: ['acidity','alkalinity','neutral','acid','base','pH','soil pH','water quality','water pH'] + }, + { + name: 'unit.turbidity', + symbol: 'NTU', + tags: ['water turbidity','water clarity','Nephelometric Turbidity Units','NTU'] + }, + { + name: 'unit.mg-per-liter', + symbol: 'mg/L', + tags: ['dissolved oxygen','water quality','mg/L'] + }, + { + name: 'unit.microsiemens-per-centimeter', + symbol: 'µS/cm', + tags: ['Electrical conductivity','water quality','soil quality','microsiemens per centimeter','µS/cm'] + }, + { + name: 'unit.millisiemens-per-meter', + symbol: 'mS/m', + tags: ['Electrical conductivity','water quality','soil quality','millisiemens per meter','mS/m'] + }, + { + name: 'unit.siemens-per-meter', + symbol: 'S/m', + tags: ['Electrical conductivity','water quality','soil quality','siemens per meter','S/m'] + }, + { + name: 'unit.kilogram-per-cubic-meter', + symbol: 'kg/m³', + tags: ['density','mass per unit volume','kg/m³'] + }, + { + name: 'unit.gram-per-cubic-centimeter', + symbol: 'g/cm³', + tags: ['density','mass per unit volume','g/cm³'] + }, + { + name: 'unit.kilogram-per-square-meter', + symbol: 'kg/m²', + tags: ['density','surface density','areal density','mass per unit area','kg/m²'] + }, + { + name: 'unit.milligram-per-milliliter', + symbol: 'mg/mL', + tags: ['concentration','mass per volume','mg/mL'] + }, + { + name: 'unit.pound-per-cubic-foot', + symbol: 'lb/ft³', + tags: ['Density','mass per unit volume','lb/ft³'] + }, + { + name: 'unit.ounces-per-cubic-inch', + symbol: 'oz/in³', + tags: ['density','mass per unit volume','oz/in³'] + }, + { + name: 'unit.tons-per-cubic-yard', + symbol: 'ton/yd³', + tags: ['density','mass per unit volume','ton/yd³'] + }, + { + name: 'unit.particle-density', + symbol: 'particles/mL', + tags: ['particle concentration','count','particles/mL'] + }, + { + name: 'unit.kilometers-per-liter', + symbol: 'km/L', + tags: ['fuel efficiency','km/L'] + }, + { + name: 'unit.miles-per-gallon', + symbol: 'mpg', + tags: ['fuel efficiency','mpg'] + }, + { + name: 'unit.liters-per-100-km', + symbol: 'L/100km', + tags: ['fuel efficiency','L/100km'] + }, + { + name: 'unit.gallons-per-mile', + symbol: 'gal/mi', + tags: ['fuel efficiency','gal/mi'] + }, + { + name: 'unit.liters-per-hour', + symbol: 'L/hr', + tags: ['fuel consumption','L/hr'] }, { - name: 'unit.kelvin', - symbol: 'K', - tags: ['temperature'] + name: 'unit.gallons-per-hour', + symbol: 'gal/hr', + tags: ['fuel consumption','gal/hr'] }, { - name: 'unit.fahrenheit', - symbol: '°F', - tags: ['temperature'] + name: 'unit.beats-per-minute', + symbol: 'bpm', + tags: ['heart rate','pulse','bpm'] + }, + { + name: 'unit.millimeters-of-mercury', + symbol: 'mmHg', + tags: ['blood pressure','systolic','diastolic','mmHg'] + }, + { + name: 'unit.milligrams-per-deciliter', + symbol: 'mg/dL', + tags: ['glucose','blood sugar','glucose level','mg/dL'] + }, + { + name: 'unit.g-force', + symbol: 'G', + tags: ['acceleration','gravity','force','g-load','G'] + }, + { + name: 'unit.kilonewton', + symbol: 'kN', + tags: ['force','kN'] + }, + { + name: 'unit.kilogram-force', + symbol: 'kgf', + tags: ['force','kgf'] + }, + { + name: 'unit.pound-force', + symbol: 'lbf', + tags: ['force','lbf'] + }, + { + name: 'unit.kilopound-force', + symbol: 'klbf', + tags: ['force','klbf'] + }, + { + name: 'unit.dyne', + symbol: 'dyn', + tags: ['force','dyn'] + }, + { + name: 'unit.poundal', + symbol: 'pdl', + tags: ['force','pdl'] + }, + { + name: 'unit.kip', + symbol: 'kip', + tags: ['force','kip'] + }, + { + name: 'unit.gal', + symbol: 'Gal', + tags: ['acceleration','gravity','g-force','Gal'] + }, + { + name: 'unit.gravity', + symbol: 'gravity', + tags: ['acceleration','gravity','g-force'] + }, + { + name: 'unit.hectopascal', + symbol: 'hPa', + tags: ['atmospheric pressure','air pressure','weather','altitude','flight','hPa'] + }, + { + name: 'unit.atmosphere', + symbol: 'atm', + tags: ['atmospheric pressure','air pressure','weather','altitude','flight','atm'] + }, + { + name: 'unit.millibars', + symbol: 'mb', + tags: ['atmospheric pressure','air pressure','weather','altitude','flight','mb'] + }, + { + name: 'unit.inch-of-mercury', + symbol: 'inHg', + tags: ['atmospheric pressure','air pressure','weather','altitude','flight','inHg','richter'] + }, + { + name: 'unit.richter-scale', + symbol: 'richter', + tags: ['earthquake','seismic activity','richter'] }, { name: 'unit.percentage', @@ -44,17 +1482,562 @@ export const units: Array = [ { name: 'unit.second', symbol: 's', - tags: ['time'] + tags: ['time','duration','interval','angle','second','arcsecond','sec'] }, { name: 'unit.minute', symbol: 'min', - tags: ['time'] + tags: ['time','duration','interval','angle','minute','arcminute','min'] }, { name: 'unit.hour', symbol: 'h', - tags: ['time'] + tags: ['time','duration','interval','h'] + }, + { + name: 'unit.day', + symbol: 'd', + tags: ['time','duration','interval','d'] + }, + { + name: 'unit.week', + symbol: 'wk', + tags: ['time','duration','interval','wk'] + }, + { + name: 'unit.month', + symbol: 'mo', + tags: ['time','duration','interval','mo'] + }, + { + name: 'unit.year', + symbol: 'yr', + tags: ['time','duration','interval','yr'] + }, + { + name: 'unit.cubic-foot-per-minute', + symbol: 'ft³/min', + tags: ['airflow','ventilation','HVAC','gas flow rate','CFM','flow rate','fluid flow','cubic foot per minute','ft³/min'] + }, + { + name: 'unit.cubic-meters-per-hour', + symbol: 'm³/hr', + tags: ['airflow','ventilation','HVAC','gas flow rate','cubic meters per hour','m³/hr'] + }, + { + name: 'unit.cubic-meters-per-second', + symbol: 'm³/s', + tags: ['airflow','ventilation','HVAC','gas flow rate','cubic meters per second','m³/s'] + }, + { + name: 'unit.liter-per-second', + symbol: 'L/s', + tags: ['airflow','ventilation','HVAC','gas flow rate','liter per second','L/s'] + }, + { + name: 'unit.liter-per-minute', + symbol: 'L/min', + tags: ['airflow','ventilation','HVAC','gas flow rate','liter per minute','L/min'] + }, + { + name: 'unit.gallons-per-minute', + symbol: 'GPM', + tags: ['airflow','ventilation','HVAC','gas flow rate','gallons per minute','GPM'] + }, + { + name: 'unit.cubic-foot-per-second', + symbol: 'ft³/s', + tags: ['flow rate','fluid flow','cubic foot per second','cubic feet per second','ft³/s'] + }, + { + name: 'unit.milliliters-per-minute', + symbol: 'mL/min', + tags: ['Flow rate','fluid dynamics','milliliters per minute','mL/min'] + }, + { + name: 'unit.bit', + symbol: 'bit', + tags: ['data','binary digit','information','bit'] + }, + { + name: 'unit.byte', + symbol: 'B', + tags: ['data','byte','information','storage','memory','B'] + }, + { + name: 'unit.kilobyte', + symbol: 'KB', + tags: ['data','kilobyte','KB'] + }, + { + name: 'unit.megabyte', + symbol: 'MB', + tags: ['data','megabyte','MB'] + }, + { + name: 'unit.gigabyte', + symbol: 'GB', + tags: ['data','gigabyte','GB'] + }, + { + name: 'unit.terabyte', + symbol: 'TB', + tags: ['data','terabyte','TB'] + }, + { + name: 'unit.petabyte', + symbol: 'PB', + tags: ['data','petabyte','PB'] + }, + { + name: 'unit.exabyte', + symbol: 'EB', + tags: ['data','exabyte','EB'] + }, + { + name: 'unit.zettabyte', + symbol: 'ZB', + tags: ['data','zettabyte','ZB'] + }, + { + name: 'unit.yottabyte', + symbol: 'YB', + tags: ['data','yottabyte','YB'] + }, + { + name: 'unit.bit-per-second', + symbol: 'bps', + tags: ['data transfer rate','bps'] + }, + { + name: 'unit.kilobit-per-second', + symbol: 'kbps', + tags: ['data transfer rate','kbps'] + }, + { + name: 'unit.megabit-per-second', + symbol: 'Mbps', + tags: ['data transfer rate','Mbps'] + }, + { + name: 'unit.gigabit-per-second', + symbol: 'Gbps', + tags: ['data transfer rate','Gbps'] + }, + { + name: 'unit.terabit-per-second', + symbol: 'Tbps', + tags: ['data transfer rate','Tbps'] + }, + { + name: 'unit.byte-per-second', + symbol: 'B/s', + tags: ['data transfer rate','B/s'] + }, + { + name: 'unit.kilobyte-per-second', + symbol: 'KB/s', + tags: ['data transfer rate','KB/s'] + }, + { + name: 'unit.megabyte-per-second', + symbol: 'MB/s', + tags: ['data transfer rate','MB/s'] + }, + { + name: 'unit.gigabyte-per-second', + symbol: 'GB/s', + tags: ['data transfer rate','GB/s'] + }, + { + name: 'unit.degree', + symbol: 'deg', + tags: ['angle','degree','degrees','deg'] + }, + { + name: 'unit.radian', + symbol: 'rad', + tags: ['angle','radian','radians','rad'] + }, + { + name: 'unit.gradian', + symbol: 'grad', + tags: ['angle','gradian','grades','grad'] + }, + { + name: 'unit.mil', + symbol: 'mil', + tags: ['angle','military angle','angular mil','mil'] + }, + { + name: 'unit.revolution', + symbol: 'rev', + tags: ['angle','revolution','full circle','complete turn','rev'] + }, + { + name: 'unit.siemens', + symbol: 'S', + tags: ['electrical conductance','conductance','siemens','S'] + }, + { + name: 'unit.millisiemens', + symbol: 'mS', + tags: ['electrical conductance','conductance','millisiemens','mS'] + }, + { + name: 'unit.microsiemens', + symbol: 'μS', + tags: ['electrical conductance','conductance','microsiemens','μS'] + }, + { + name: 'unit.kilosiemens', + symbol: 'kS', + tags: ['electrical conductance','conductance','kilosiemens','kS'] + }, + { + name: 'unit.megasiemens', + symbol: 'MS', + tags: ['electrical conductance','conductance','megasiemens','MS'] + }, + { + name: 'unit.gigasiemens', + symbol: 'GS', + tags: ['electrical conductance','conductance','gigasiemens','GS'] + }, + { + name: 'unit.farad', + symbol: 'F', + tags: ['electric capacitance','capacitance','farad','F'] + }, + { + name: 'unit.millifarad', + symbol: 'mF', + tags: ['electric capacitance','capacitance','millifarad','mF'] + }, + { + name: 'unit.microfarad', + symbol: 'μF', + tags: ['electric capacitance','capacitance','microfarad','μF'] + }, + { + name: 'unit.nanofarad', + symbol: 'nF', + tags: ['electric capacitance','capacitance','nanofarad','nF'] + }, + { + name: 'unit.picofarad', + symbol: 'pF', + tags: ['electric capacitance','capacitance','picofarad','pF'] + }, + { + name: 'unit.kilofarad', + symbol: 'kF', + tags: ['electric capacitance','capacitance','kilofarad','kF'] + }, + { + name: 'unit.megafarad', + symbol: 'MF', + tags: ['electric capacitance','capacitance','megafarad','MF'] + }, + { + name: 'unit.gigafarad', + symbol: 'GF', + tags: ['electric capacitance','capacitance','gigafarad','GF'] + }, + { + name: 'unit.terfarad', + symbol: 'TF', + tags: ['electric capacitance','capacitance','terafarad','TF'] + }, + { + name: 'unit.farad-per-meter', + symbol: 'F/m', + tags: ['electric permittivity','farad per meter','F/m'] + }, + { + name: 'unit.tesla', + symbol: 'T', + tags: ['magnetic field','magnetic field strength','tesla','T','magnetic flux density'] + }, + { + name: 'unit.gauss', + symbol: 'G', + tags: ['magnetic field','magnetic field strength','gauss','G','magnetic flux density'] + }, + { + name: 'unit.kilogauss', + symbol: 'kG', + tags: ['magnetic field','magnetic field strength','kilogauss','kG','magnetic flux density'] + }, + { + name: 'unit.millitesla', + symbol: 'mT', + tags: ['magnetic field','magnetic field strength','millitesla','mT'] + }, + { + name: 'unit.microtesla', + symbol: 'μT', + tags: ['magnetic field','magnetic field strength','microtesla','μT'] + }, + { + name: 'unit.nanotesla', + symbol: 'nT', + tags: ['magnetic field','magnetic field strength','nanotesla','nT'] + }, + { + name: 'unit.kilotesla', + symbol: 'kT', + tags: ['magnetic field','magnetic field strength','kilotesla','kT'] + }, + { + name: 'unit.megatesla', + symbol: 'MT', + tags: ['magnetic field','magnetic field strength','megatesla','MT'] + }, + { + name: 'unit.millitesla-square-meters', + symbol: 'millitesla square meters', + tags: ['magnetic field','millitesla square meters'] + }, + { + name: 'unit.gamma', + symbol: 'γ', + tags: ['magnetic flux density','gamma','γ'] + }, + { + name: 'unit.lambda', + symbol: 'λ', + tags: ['wavelength','lambda','λ'] + }, + { + name: 'unit.square-meter-per-second', + symbol: 'm²/s', + tags: ['kinematic viscosity','m²/s'] + }, + { + name: 'unit.square-centimeter-per-second', + symbol: 'cm²/s', + tags: ['kinematic viscosity','cm²/s'] + }, + { + name: 'unit.stoke', + symbol: 'St', + tags: ['kinematic viscosity','stokes','St'] + }, + { + name: 'unit.centistokes', + symbol: 'cSt', + tags: ['kinematic viscosity','centistokes','cSt'] + }, + { + name: 'unit.square-foot-per-second', + symbol: 'ft²/s', + tags: ['kinematic viscosity','ft²/s'] + }, + { + name: 'unit.square-inch-per-second', + symbol: 'in²/s', + tags: ['kinematic viscosity','in²/s'] + }, + { + name: 'unit.pascal-second', + symbol: 'Pa·s', + tags: ['dynamic viscosity','viscosity','fluid mechanics','pascal-second','Pa·s'] + }, + { + name: 'unit.centipoise', + symbol: 'cP', + tags: ['viscosity','dynamic viscosity','fluid viscosity','centipoise','cP'] + }, + { + name: 'unit.poise', + symbol: 'P', + tags: ['viscosity','dynamic viscosity','fluid viscosity','poise','P'] + }, + { + name: 'unit.reynolds', + symbol: 'Re', + tags: ['fluid flow regime','fluid mechanics','reynolds','Re'] + }, + { + name: 'unit.pound-per-foot-hour', + symbol: 'lb/(ft·h)', + tags: ['pound per foot-hour','lb/(ft·h)'] + }, + { + name: 'unit.newton-second-per-square-meter', + symbol: 'N·s/m²', + tags: ['newton second per square meter','N·s/m²'] + }, + { + name: 'unit.dyne-second-per-square-centimeter', + symbol: 'dyn·s/cm²', + tags: ['dyne second per square centimeter','dyn·s/cm²'] + }, + { + name: 'unit.kilogram-per-meter-second', + symbol: 'kg/(m·s)', + tags: ['kilogram per meter-second','kg/(m·s)'] + }, + { + name: 'unit.tesla-square-meters', + symbol: 'T/m²', + tags: ['magnetic flux density','tesla square meters','T/m²'] + }, + { + name: 'unit.maxwell', + symbol: 'Mx', + tags: ['magnetic flux','magnetic field','maxwell','Mx'] + }, + { + name: 'unit.tesla-per-meter', + symbol: 'T/m', + tags: ['magnetic field','tesla per meter','T/m'] + }, + { + name: 'unit.gauss-per-centimeter', + symbol: 'G/cm', + tags: ['magnetic field','gauss per centimeter','G/cm'] + }, + { + name: 'unit.weber', + symbol: 'Wb', + tags: ['magnetic flux','weber','Wb'] + }, + { + name: 'unit.microweber', + symbol: 'µWb', + tags: ['magnetic flux','microweber','µWb'] + }, + { + name: 'unit.milliweber', + symbol: 'mWb', + tags: ['magnetic flux','milliweber','mWb'] + }, + { + name: 'unit.gauss-square-centimeter', + symbol: 'G·cm²', + tags: ['magnetic flux','gauss-square centimeter','G·cm²'] + }, + { + name: 'unit.kilogauss-square-centimeter', + symbol: 'kG·cm²', + tags: ['magnetic flux','kilogauss-square centimeter','kG·cm²'] + }, + { + name: 'unit.henry', + symbol: 'H', + tags: ['inductance','magnetic induction','H'] + }, + { + name: 'unit.millihenry', + symbol: 'mH', + tags: ['inductance','millihenry','mH'] + }, + { + name: 'unit.microhenry', + symbol: 'µH', + tags: ['inductance','microhenry','µH'] + }, + { + name: 'unit.nanohenry', + symbol: 'nH', + tags: ['inductance','nanohenry','nH'] + }, + { + name: 'unit.henry-per-meter', + symbol: 'H/m', + tags: ['magnetic permeability','henry per meter','H/m'] + }, + { + name: 'unit.tesla-meter-per-ampere', + symbol: 'T·m/A', + tags: ['magnetic field','Tesla Meter per Ampere','T·m/A','magnetic flux'] + }, + { + name: 'unit.gauss-per-oersted', + symbol: 'G/Oe', + tags: ['magnetic field','Gauss per Oersted','G/Oe'] + }, + { + name: 'unit.kilogram-per-mole', + symbol: 'kg/mol', + tags: ['molar mass','kilogram per mole','kg/mol'] + }, + { + name: 'unit.gram-per-mole', + symbol: 'g/mol', + tags: ['molar mass','gram per mole','g/mol'] + }, + { + name: 'unit.milligram-per-mole', + symbol: 'mg/mol', + tags: ['molar mass','milligram per mole','mg/mol'] + }, + { + name: 'unit.joule-per-mole', + symbol: 'J/mol', + tags: ['molar energy','joule per mole','J/mol'] + }, + { + name: 'unit.joule-per-mole-kelvin', + symbol: 'J/(mol·K)', + tags: ['molar heat capacity','joule per mole-kelvin','J/(mol·K)'] + }, + { + name: 'unit.millivolts-per-meter', + symbol: 'mV/m', + tags: ['electric field strength','millivolts per meter','mV/m'] + }, + { + name: 'unit.volts-per-meter', + symbol: 'V/m', + tags: ['electric field strength','volts per meter','V/m'] + }, + { + name: 'unit.kilovolts-per-meter', + symbol: 'kV/m', + tags: ['electric field strength','kilovolts per meter','kV/m'] + }, + { + name: 'unit.radian-per-second', + symbol: 'rad/s', + tags: ['angular velocity','rotation speed','rad/s'] + }, + { + name: 'unit.radian-per-second-squared', + symbol: 'rad/s²', + tags: ['angular acceleration','rotation rate of change','rad/s²'] + }, + { + name: 'unit.revolutions-per-minute-per-second', + symbol: 'rpm/s', + tags: ['angular acceleration','rotation rate of change','rpm/s'] + }, + { + name: 'unit.revolutions-per-minute-per-second-squared', + symbol: 'rpm/s²', + tags: ['angular acceleration','rotation rate of change','rpm/s²'] + }, + { + name: 'unit.deg-per-second', + symbol: 'deg/s', + tags: ['angular velocity','degrees per second','deg/s'] + }, + { + name: 'unit.degrees-brix', + symbol: '°Bx', + tags: ['sugar content','fruit ripeness','Bx'] + }, + { + name: 'unit.katal', + symbol: 'kat', + tags: ['catalytic activity','enzyme activity','kat'] + }, + { + name: 'unit.katal-per-cubic-metre', + symbol: 'kat/m³', + tags: ['catalytic activity concentration','enzyme concentration','kat/m³'] } ]; 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 82f10b7f4c..ffcea42d95 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3861,13 +3861,424 @@ "ago": "ago" }, "unit": { + "millimeter": "Millimeter", + "centimeter": "Centimeter", + "angstrom": "Angstrom", + "nanometer": "Nanometer", + "micrometer": "Micrometer", + "meter": "Meter", + "kilometer": "Kilometer", + "inch": "Inch", + "foot": "Foot", + "yard": "Yard", + "mile": "Mile", + "nautical-mile": "Nautical Mile", + "astronomical-unit": "Astronomical Unit", + "reciprocal-metre": "Reciprocal Metre", + "meter-per-meter": "Meter per meter", + "steradian": "Steradian", + "thou": "Thou", + "barleycorn": "Barleycorn", + "hand": "Hand", + "chain": "Chain", + "furlong": "Furlong", + "league": "League", + "fathom": "Fathom", + "cable": "Cable", + "link": "Link", + "rod": "Rod", + "nanogram": "Nanogram", + "microgram": "Microgram", + "milligram": "Milligram", + "gram": "Gram", + "kilogram": "Kilogram", + "tonne": "Tonne", + "ounce": "Ounce", + "pound": "Pound", + "stone": "Stone", + "hundredweight-count": "Hundredweight count", + "short-tons": "Short tons", + "dalton": "Dalton", + "grain": "Grain", + "drachm": "Drachm", + "quarter": "Quarter", + "slug": "Slug", + "carat": "Carat", + "cubic-millimeter": "Cubic Millimeter", + "cubic-centimeter": "Cubic Centimeter", + "cubic-meter": "Cubic Meter/s", + "cubic-kilometer": "Cubic Kilometers", + "microliter": "Microliter", + "milliliter": "Milliliter", + "liter": "Liter", + "hectoliter": "Hectolitre", + "cubic-inch": "Cubic Inch", + "cubic-foot": "Cubic Foot", + "cubic-yard": "Cubic Yards", + "fluid-ounce": "Fluid Ounce", + "pint": "Pint", + "quart": "Quart", + "gallon": "Gallon", + "oil-barrels": "Oil Barrels", + "cubic-meter-per-kilogram": "Cubic Meter per Kilogram", + "gill": "Gill", + "hogshead": "Hogshead", + "teaspoon": "Teaspoon", + "tablespoon": "Tablespoon", + "cup": "Cup", "celsius": "Celsius", "kelvin": "Kelvin", + "rankine": "Rankine", "fahrenheit": "Fahrenheit", "percentage": "Percentage", + "meter-per-second": "Meter per Second", + "kilometer-per-hour": "Kilometer per Hour", + "foot-per-second": "Foot per Second", + "mile-per-hour": "Mile per Hour", + "knot": "Knot", + "millimeters-per-minute": "Millimeters per minute", + "kilometer-per-hour-squared": "Kilometer per hour squared", + "foot-per-second-squared": "Foot per second squared", + "pascal": "Pascal", + "kilopascal": "Kilopascal", + "megapascal": "Megapascal", + "gigapascal": "Gigapascal", + "millibar": "Millibar", + "bar": "Bar", + "kilobar": "Kilobar", + "newton": "Newton", + "newton-meter": "Newton meter", + "foot-pounds": "Foot-pounds", + "inch-pounds": "Inch-pounds", + "newton-per-meter": "Newton per meter", + "atmospheres": "Atmospheres", + "pounds-per-square-inch": "Pounds per Square Inch", + "torr": "Torr", + "inches-of-mercury": "Inches of Mercury", + "pascal-per-square-meter": "Pascal per Square Meter", + "pound-per-square-inch": "Pound per Square Inch", + "newton-per-square-meter": "Newton per Square Meter", + "kilogram-force-per-square-meter": "Kilogram-force per Square Meter", + "pascal-per-square-centimeter": "Pascal per Square Centimeter", + "ton-force-per-square-inch": "Ton-force per Square Inch", + "kilonewton-per-square-meter": "Kilonewton per Square Meter", + "newton-per-square-millimeter": "Newton per Square Millimeter", + + "microjoule": "Microjoule", + "millijoule": "Millijoule", + "joule": "Joule", + "kilojoule": "Kilojoule", + "megajoule": "Megajoule", + "gigajoule": "Gigajoule", + "watt-hour": "Watt-hour", + "kilowatt-hour": "Kilowatt-hour", + "electron-volts": "Electron volts", + "joules-per-coulomb": "Joules per Coulomb", + "british-thermal-unit": "British Thermal Units", + "foot-pound": "Foot-pound", + "calorie": "Calorie", + "small-calorie": "Small Calorie", + "kilocalorie": "Kilocalorie", + "joule-per-kelvin": "Joule per Kelvin", + "joule-per-kilogram-kelvin": "Joule per Kilogram-Kelvin", + "joule-per-kilogram": "Joule per Kilogram", + "watt-per-meter-kelvin": "Watt per Meter-Kelvin", + "joule-per-cubic-meter": "Joule per Cubic Meter", + "therm": "Therm", + "electric-dipole-moment": "Electric Dipole Moment", + "magnetic-dipole-moment": "Magnetic Dipole Moment", + "debye": "Debye", + "coulomb-per-square-meter-per-volt": "Coulomb per Square Meter per Volt", + "milliwatt": "Milliwatt", + "microwatt": "Microwatt", + "watt": "Watt", + "kilowatt": "Kilowatt", + "megawatt": "Megawatt", + "gigawatt": "Gigawatt", + "metric-horsepower": "Metric Horsepower", + "milliwatt-per-square-centimeter": "Milliwatts per square centimeter", + "watt-per-square-centimeter": "Watts per square centimeter", + "kilowatt-per-square-centimeter": "Kilowatts per square centimeter", + "milliwatt-per-square-meter": "Milliwatts per square meter", + "watt-per-square-meter": "Watts per square meter", + "kilowatt-per-square-meter": "Kilowatts per square meter", + "watt-per-square-inch": "Watts per square inch", + "kilowatt-per-square-inch": "Kilowatts per square inch", + "horsepower": "Horsepower", + "btu-per-hour": "British thermal units/hour", + "coulomb": "Coulomb", + "millicoulomb": "Millicoulombs", + "microcoulomb": "Microcoulomb", + "picocoulomb": "Picocoulomb", + "coulomb-per-meter": "Coulomb per meter", + "coulomb-per-cubic-meter": "Coulomb per Cubic Meter", + "coulomb-per-square-meter": "Coulomb per Square Meter", + "square-millimeter": "Square Millimeter", + "square-centimeter": "Square Centimeter", + "square-meter": "Square Meter", + "hectare": "Hectare", + "square-kilometer": "Square Kilometer", + "square-inch": "Square Inch", + "square-foot": "Square Foot", + "square-yard": "Square Yard", + "acre": "Acre", + "square-mile": "Square Mile", + "are": "Are", + "barn": "Barn", + "circular-inch": "Circular Inch", + "milliampere-hour": "Milliampere-hour", + "milliampere-hour-tags": "electric current, current flow, electric charge, current capacity, flow of electricity, electrical flow, milliampere-hour, milliampere-hours, mAh", + "ampere-hours": "Ampere-hours", + "ampere-hours-tags": "electric current, current flow, electric charge, current capacity, flow of electricity, electrical flow, ampere, ampere-hours, Ah", + "kiloampere-hours": "Kiloampere-hours", + "kiloampere-hours-tags": "electric current, current flow, electric charge, current capacity, flow of electricity, electrical flow, kiloampere-hours, kiloampere-hour, kAh", + "nanoampere": "Nanoampere", + "nanoampere-tags": "current, amperes, nanoampere, nA", + "picoampere": "Picoampere", + "picoampere-tags": "current, amperes, picoampere, pA", + "microampere": "Microampere", + "microampere-tags": "electric current, microampere, microamperes, μA", + "milliampere": "Milliampere", + "milliampere-tags": "electric current, milliampere, milliamperes, mA", + "ampere": "Ampere", + "ampere-tags": "electric current, current flow, flow of electricity, electrical flow, ampere, amperes, amperage, A", + "kiloamperes": "Kiloamperes", + "kiloamperes-tags": "electric current, current flow, kiloamperes, kA", + "microampere-per-square-centimeter": "Microampere per square centimeter", + "microampere-per-square-centimeter-tags": "Current density, microampere per square centimeter, µA/cm²", + "ampere-per-square-meter": "Ampere per Square Meter", + "ampere-per-square-meter-tags": "current density, current per unit area, ampere per square meter, A/m²", + "ampere-per-meter": "Ampere per Meter", + "ampere-per-meter-tags": "magnetic field strength, magnetic field intensity, ampere per meter, A/m", + "oersted": "Oersted", + "oersted-tags": "magnetic field, oersted, Oe", + "bohr-magneton": "Bohr Magneton", + "bohr-magneton-tags": "atomic physics, magnetic moment, bohr magneton, μB", + "ampere-meter-squared": "Ampere-Meter Squared", + "ampere-meter-squared-tags": "magnetic moment, dipole moment, ampere-meter squared, A·m²", + "ampere-meter": "Ampere-Meter", + "ampere-meter-tags": "magnetic field, current loop, ampere-meter, A·m", + "nanovolt": "Nanovolt", + "picovolt": "Picovolt", + "millivolts": "Millivolts", + "microvolts": "Microvolts", + "volt": "Volt", + "kilovolts": "Kilovolts", + "dbmV": "dBmV", + "volt-meter": "Volt-Meter", + "kilovolt-meter": "Kilovolt-Meter", + "megavolt-meter": "Megavolt-Meter", + "microvolt-meter": "Microvolt-Meter", + "millivolt-meter": "Millivolt-Meter", + "nanovolt-meter": "Nanovolt-Meter", + "ohm": "Ohm", + "microohm": "Microohm", + "milliohm": "Milliohm", + "kilohm": "Kilohm", + "megohm": "Megohm", + "gigohm": "Gigohm", + "hertz": "Hertz", + "kilohertz": "Kilohertz", + "megahertz": "Megahertz", + "gigahertz": "Gigahertz", + "rpm": "Revolutions Per Minute", + "candela-per-square-meter": "Candela per square meter", + "candela": "Candela", + "lumen": "Lumen", + "lux": "Lux", + "foot-candle": "Foot-candle", + "lumen-per-square-meter": "Lumen per square meter", + "lux-second": "Lux second", + "lumen-second": "Lumen second", + "lumens-per-watt": "Lumens per watt", + "absorbance": "Absorbance", + "mole": "Mole", + "nanomole": "Nanomole", + "micromole": "MicroMole", + "millimole": "Millimole", + "kilomole": "Kilomole", + "mole-per-cubic-meter": "Mole per Cubic Meter", + "battery": "Battery", + "rssi": "RSSI", + "ppm": "Parts Per Million", + "ppb": "Parts Per Billion", + "micrograms-per-cubic-meter": "Micrograms per Cubic Meter", + "aqi": "AQI", + "gram-per-cubic-meter": "Gram per cubic meter", + "gram-per-kilogram": "Specific Humidity", + "millimeters-per-second": "Millimeters per second", + "neper": "Neper", + "bel": "Bel", + "decibel": "Decibel", + "meters-per-second-squared": "Meters per second squared", + "becquerel": "Becquerel", + "curie": "Curie", + "gray": "Gray", + "sievert": "Sievert", + "roentgen": "Roentgen", + "cps": "Counts per Second", + "rad": "Rad", + "rem": "Rem", + "dps": "Disintegrations per second", + "rutherford": "Rutherford", + "coulombs-per-kilogram": "Coulombs per kilogram", + "becquerels-per-cubic-meter": "Becquerels per cubic meter", + "curies-per-liter": "Curies per liter", + "becquerels-per-second": "Becquerels per second", + "curies-per-second": "Curies per second", + "gy-per-second": "Gray per Second", + "watt-per-steradian": "Watt per Steradian", + "watt-per-square-metre-steradian": "Watt per Square Metre-Steradian", + "ph-level": "pH Level", + "turbidity": "Turbidity", + "mg-per-liter": "Milligrams per liter", + "microsiemens-per-centimeter": "Microsiemens per centimeter", + "millisiemens-per-meter": "Millisiemens per meter", + "siemens-per-meter": "Siemens per meter", + "kilogram-per-cubic-meter": "Kilogram per cubic meter", + "gram-per-cubic-centimeter": "Gram per cubic centimeter", + "kilogram-per-square-meter": "Kilogram per square metre", + "milligram-per-milliliter": "Milligram per milliliter", + "pound-per-cubic-foot": "Pound per cubic foot", + "ounces-per-cubic-inch": "Ounces per cubic inch", + "tons-per-cubic-yard": "Tons per cubic yard", + "particle-density": "Particle density", + "kilometers-per-liter": "Kilometers per liter", + "miles-per-gallon": "Miles per gallon", + "liters-per-100-km": "Liters per 100 km", + "gallons-per-mile": "Gallons per mile", + "liters-per-hour": "Liters per hour", + "gallons-per-hour": "Gallons per hour", + "beats-per-minute": "Beats per minute", + "millimeters-of-mercury": "Millimeters of mercury", + "milligrams-per-deciliter": "Milligrams per deciliter", + "g-force": "G-force", + "kilonewton": "Kilonewton", + "kilogram-force": "Kilogram-Force", + "pound-force": "Pound-Force", + "kilopound-force": "Kilopound-Force", + "dyne": "Dyne", + "poundal": "Poundal", + "kip": "Kip", + "gal": "Gal", + "gravity": "Gravity", + "hectopascal": "Hectopascal", + "atmosphere": "Atmosphere", + "millibars": "Millibars", + "inch-of-mercury": "One inch of mercury", + "richter-scale": "Richter Scale", "second": "Second", "minute": "Minute", - "hour": "Hour" + "hour": "Hour", + "day": "Day", + "week": "Week", + "month": "Month", + "year": "Year", + "cubic-foot-per-minute": "Cubic Foot Per Minute", + "cubic-meters-per-hour": "Cubic Meters Per Hour", + "cubic-meters-per-second": "Cubic Meters Per Second", + "liter-per-second": "Liter Per Second", + "liter-per-minute": "Liter Per Minute", + "gallons-per-minute": "Gallons Per Minute", + "cubic-foot-per-second": "Cubic foot per second", + "milliliters-per-minute": "Milliliters per minute", + "bit": "Bit", + "byte": "Byte", + "kilobyte": "Kilobyte", + "megabyte": "Megabyte", + "gigabyte": "Gigabyte", + "terabyte": "Terabyte", + "petabyte": "Petabyte", + "exabyte": "Exabyte", + "zettabyte": "Zettabyte", + "yottabyte": "Yottabyte", + "bit-per-second": "Bit per second", + "kilobit-per-second": "Kilobit per second", + "megabit-per-second": "Megabit per second", + "gigabit-per-second": "Gigabit per second", + "terabit-per-second": "Terabit per second", + "byte-per-second": "Byte per second", + "kilobyte-per-second": "Kilobyte per second", + "megabyte-per-second": "Megabyte per second", + "gigabyte-per-second": "Gigabyte per second", + "degree": "Degree", + "radian": "Radian", + "gradian": "Gradian", + "mil": "Mil", + "revolution": "Revolution", + "siemens": "Siemens", + "millisiemens": "Millisiemens", + "microsiemens": "Microsiemens", + "kilosiemens": "Kilosiemens", + "megasiemens": "Megasiemens", + "gigasiemens": "Gigasiemens", + "farad": "Farad", + "millifarad": "Millifarad", + "microfarad": "Microfarad", + "nanofarad": "Nanofarad", + "picofarad": "Picofarad", + "kilofarad": "Kilofarad", + "megafarad": "Megafarad", + "gigafarad": "Gigafarad", + "terfarad": "Terfarad", + "farad-per-meter": "Farad per Meter", + "tesla": "Tesla", + "gauss": "Gauss", + "kilogauss": "Kilogauss", + "millitesla": "Millitesla", + "microtesla": "Microtesla", + "nanotesla": "Nanotesla", + "kilotesla": "Kilotesla", + "megatesla": "Megatesla", + "millitesla-square-meters": "millitesla square meters", + "gamma": "Gamma", + "lambda": "Lambda", + "square-meter-per-second": "Square meter per second", + "square-centimeter-per-second": "Square centimeter per second", + "stoke": "Stoke", + "centistokes": "Centistokes", + "square-foot-per-second": "Square foot per second", + "square-inch-per-second": "Square inch per second", + "pascal-second": "Pascal-second", + "centipoise": "Centipoise", + "poise": "Poise", + "reynolds": "Reynolds", + "pound-per-foot-hour": "Pound per foot-hour", + "newton-second-per-square-meter": "Newton second per square meter", + "dyne-second-per-square-centimeter": "Dyne second per square centimeter", + "kilogram-per-meter-second": "Kilogram per meter-second", + "tesla-square-meters": "Tesla square meters", + "maxwell": "Maxwell", + "tesla-per-meter": "Tesla per Meter", + "gauss-per-centimeter": "Gauss per Centimeter", + "weber": "Weber", + "microweber": "Microweber", + "milliweber": "Milliweber", + "gauss-square-centimeter": "Gauss-Square Centimeter", + "kilogauss-square-centimeter": "Kilogauss-Square Centimeter", + "henry": "Henry", + "millihenry": "Millihenry", + "microhenry": "Microhenry", + "nanohenry": "Nanohenry", + "henry-per-meter": "Henry per Meter", + "tesla-meter-per-ampere": "Tesla Meter per Ampere", + "gauss-per-oersted": "Gauss per Oersted", + "kilogram-per-mole": "Kilogram per mole", + "gram-per-mole": "Gram per mole", + "milligram-per-mole": "Milligram per mole", + "joule-per-mole": "Joule per Mole", + "joule-per-mole-kelvin": "Joule per Mole-Kelvin", + "millivolts-per-meter": "Millivolts per meter", + "volts-per-meter": "Volts per meter", + "kilovolts-per-meter": "Kilovolts per meter", + "radian-per-second": "Radian per second", + "radian-per-second-squared": "Radian per second squared", + "revolutions-per-minute-per-second": "Angular acceleration", + "revolutions-per-minute-per-second-squared": "Angular Acceleration", + "deg-per-second": "deg/s", + "degrees-brix": "Degrees Brix", + "katal": "Katal", + "katal-per-cubic-metre": "Katal per Cubic Metre" }, "user": { "user": "User", From f7b60e1c0e1b7ef27a42bcd80b54d942c566b33b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 11 Jul 2023 10:40:04 +0300 Subject: [PATCH 053/168] UI: Redesign user menu: move profile and security menu item to account item --- ui-ngx/src/app/core/auth/auth.service.ts | 2 +- ui-ngx/src/app/core/services/menu.service.ts | 84 +++++++++++++++++++ ui-ngx/src/app/modules/home/home.component.ts | 9 +- .../modules/home/menu/side-menu.component.ts | 12 ++- .../pages/account/account-routing.module.ts | 54 ++++++++++++ .../home/pages/account/account.module.ts | 28 +++++++ .../modules/home/pages/home-pages.module.ts | 4 +- .../pages/profile/profile-routing.module.ts | 9 +- .../pages/security/security-routing.module.ts | 9 +- .../components/user-menu.component.html | 8 +- .../shared/components/user-menu.component.ts | 8 +- .../assets/locale/locale.constant-en_US.json | 4 + 12 files changed, 210 insertions(+), 21 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/account/account.module.ts diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 87c6561315..f1af3b745a 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -244,7 +244,7 @@ export class AuthService { if (authState && authState.authUser) { if (authState.authUser.authority === Authority.TENANT_ADMIN || authState.authUser.authority === Authority.CUSTOMER_USER) { if ((this.userHasDefaultDashboard(authState) && authState.forceFullscreen) || authState.authUser.isPublic) { - if (path === 'profile' || path === 'security') { + if (path.startsWith('account')) { if (this.userHasProfile(authState.authUser)) { return false; } else { diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index b33c552eb1..507ed01984 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -262,6 +262,34 @@ export class MenuService { isMdiIcon: true } ] + }, + { + id: 'account', + name: 'profile.profile', + type: 'link', + path: '/account', + disabled: true, + icon: 'mdi:message-badge', + isMdiIcon: true, + pages: [ + { + id: 'personal_info', + name: 'account.personal-info', + fullName: 'account.personal-info', + type: 'link', + path: '/account/profile', + icon: 'mdi:badge-account-horizontal', + isMdiIcon: true + }, + { + id: 'security', + name: 'security.security', + fullName: 'security.security', + type: 'link', + path: '/account/security', + icon: 'lock' + } + ] } ); return sections; @@ -634,6 +662,34 @@ export class MenuService { icon: 'track_changes' } ] + }, + { + id: 'account', + name: 'profile.profile', + type: 'link', + path: '/account', + disabled: true, + icon: 'mdi:message-badge', + isMdiIcon: true, + pages: [ + { + id: 'personal_info', + name: 'account.personal-info', + fullName: 'account.personal-info', + type: 'link', + path: '/account/profile', + icon: 'mdi:badge-account-horizontal', + isMdiIcon: true + }, + { + id: 'security', + name: 'security.security', + fullName: 'security.security', + type: 'link', + path: '/account/security', + icon: 'lock' + } + ] } ); return sections; @@ -885,6 +941,34 @@ export class MenuService { icon: 'inbox' } ] + }, + { + id: 'account', + name: 'profile.profile', + type: 'link', + path: '/account', + disabled: true, + icon: 'mdi:message-badge', + isMdiIcon: true, + pages: [ + { + id: 'personal_info', + name: 'account.personal-info', + fullName: 'account.personal-info', + type: 'link', + path: '/account/profile', + icon: 'mdi:badge-account-horizontal', + isMdiIcon: true + }, + { + id: 'security', + name: 'security.security', + fullName: 'security.security', + type: 'link', + path: '/account/security', + icon: 'lock' + } + ] } ); return sections; diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index 6e045b9786..1ab9cea1dc 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { AfterViewInit, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { fromEvent } from 'rxjs'; import { Store } from '@ngrx/store'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; @@ -27,10 +27,10 @@ import { MediaBreakpoints } from '@shared/models/constants'; import screenfull from 'screenfull'; import { MatSidenav } from '@angular/material/sidenav'; import { AuthState } from '@core/auth/auth.models'; -import { WINDOW } from '@core/services/window.service'; import { instanceOfSearchableComponent, ISearchableComponent } from '@home/models/searchable-component.models'; import { ActiveComponentService } from '@core/services/active-component.service'; import { RouterTabsComponent } from '@home/components/router-tabs.component'; +import { Router } from '@angular/router'; @Component({ selector: 'tb-home', @@ -65,8 +65,8 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni hideLoadingBar = false; constructor(protected store: Store, - @Inject(WINDOW) private window: Window, private activeComponentService: ActiveComponentService, + private router: Router, public breakpointObserver: BreakpointObserver) { super(store); } @@ -120,7 +120,8 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni } goBack() { - this.window.history.back(); + const dashboardId = this.authState.userDetails.additionalInfo.defaultDashboardId; + this.router.navigate(['dashboard', dashboardId]).then(() => {}); } activeComponentChanged(activeComponent: any) { diff --git a/ui-ngx/src/app/modules/home/menu/side-menu.component.ts b/ui-ngx/src/app/modules/home/menu/side-menu.component.ts index f6e1f30624..cf3e5ca4db 100644 --- a/ui-ngx/src/app/modules/home/menu/side-menu.component.ts +++ b/ui-ngx/src/app/modules/home/menu/side-menu.component.ts @@ -17,6 +17,8 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { MenuService } from '@core/services/menu.service'; import { MenuSection } from '@core/services/menu.models'; +import { Observable, of } from 'rxjs'; +import { mergeMap, share } from 'rxjs/operators'; @Component({ selector: 'tb-side-menu', @@ -26,15 +28,23 @@ import { MenuSection } from '@core/services/menu.models'; }) export class SideMenuComponent implements OnInit { - menuSections$ = this.menuService.menuSections(); + menuSections$: Observable>; constructor(private menuService: MenuService) { + this.menuSections$ = this.menuService.menuSections().pipe( + mergeMap((sections) => this.filterSections(sections)), + share() + ); } trackByMenuSection(index: number, section: MenuSection){ return section.id; } + private filterSections(sections: Array): Observable> { + return of(sections.filter(section => !section.disabled)); + } + ngOnInit() { } 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 new file mode 100644 index 0000000000..bb63b361b6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts @@ -0,0 +1,54 @@ +/// +/// 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. +/// + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { RouterTabsComponent } from '@home/components/router-tabs.component'; +import { Authority } from '@shared/models/authority.enum'; +import { securityRoutes } from '@home/pages/security/security-routing.module'; +import { profileRoutes } from '@home/pages/profile/profile-routing.module'; + +const routes: Routes = [ + { + path: 'account', + component: RouterTabsComponent, + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + breadcrumb: { + label: 'account.account', + icon: 'account_circle' + } + }, + children: [ + { + path: '', + children: [], + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + redirectTo: '/account/profile', + } + }, + ...profileRoutes, + ...securityRoutes + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AccountRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/account/account.module.ts b/ui-ngx/src/app/modules/home/pages/account/account.module.ts new file mode 100644 index 0000000000..df178607ce --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/account/account.module.ts @@ -0,0 +1,28 @@ +/// +/// 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. +/// + +import { NgModule } from '@angular/core'; +import { AccountRoutingModule } from '@home/pages/account/account-routing.module'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + declarations: [ ], + imports: [ + CommonModule, + AccountRoutingModule + ] +}) +export class AccountModule { } diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index b1f4715c33..005c47f787 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -42,6 +42,7 @@ import { AlarmModule } from '@home/pages/alarm/alarm.module'; import { EntitiesModule } from '@home/pages/entities/entities.module'; import { FeaturesModule } from '@home/pages/features/features.module'; import { NotificationModule } from '@home/pages/notification/notification.module'; +import { AccountModule } from '@home/pages/account/account.module'; @NgModule({ exports: [ @@ -70,7 +71,8 @@ import { NotificationModule } from '@home/pages/notification/notification.module ApiUsageModule, OtaUpdateModule, UserModule, - VcModule + VcModule, + AccountModule ] }) export class HomePagesModule { } diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts b/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts index c14e450745..334174194c 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts @@ -40,7 +40,7 @@ export class UserProfileResolver implements Resolve { } } -const routes: Routes = [ +export const profileRoutes: Routes = [ { path: 'profile', component: ProfileComponent, @@ -59,6 +59,13 @@ const routes: Routes = [ } ]; +const routes: Routes = [ + { + path: 'profile', + redirectTo: 'account/profile' + } +]; + @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], diff --git a/ui-ngx/src/app/modules/home/pages/security/security-routing.module.ts b/ui-ngx/src/app/modules/home/pages/security/security-routing.module.ts index d2820e0184..f6da1dabd2 100644 --- a/ui-ngx/src/app/modules/home/pages/security/security-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/security/security-routing.module.ts @@ -53,7 +53,7 @@ export class UserTwoFAProvidersResolver implements Resolve
- - - - - -
-
- -
-
-
-
- - - - - - -
-
-
-
- - -
- +
+ + + +
diff --git a/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.scss b/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.scss index 849b646234..afd8d1cfcb 100644 --- a/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.scss +++ b/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.scss @@ -14,36 +14,9 @@ * limitations under the License. */ :host { - .tb-material-icons-dialog { - position: relative; - } - .tb-icons-load { - top: 64px; - z-index: 3; - background: rgba(255, 255, 255, .75); - } -} - -:host ::ng-deep { - .tb-material-icons-dialog { - button.mat-mdc-button-base.tb-select-icon-button { - width: 56px; - min-width: 56px; - height: 56px; - padding: 16px; - margin: 10px; - border: solid 1px #ffa500; - border-radius: 0; - line-height: 0; - display: inline-block; - vertical-align: baseline; - .mat-icon { - width: 24px; - margin: 0; - height: 24px; - vertical-align: initial; - font-size: 24px; - } - } + .tb-close-button { + position: absolute; + top: 6px; + right: 6px; } } diff --git a/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.ts index e63b6a0c9c..b6321c966d 100644 --- a/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.ts @@ -14,18 +14,12 @@ /// limitations under the License. /// -import { AfterViewInit, Component, Inject, OnInit, QueryList, ViewChildren } from '@angular/core'; +import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { Router } from '@angular/router'; import { DialogComponent } from '@shared/components/dialog.component'; -import { UtilsService } from '@core/services/utils.service'; -import { UntypedFormControl } from '@angular/forms'; -import { merge, Observable } from 'rxjs'; -import { delay, map, mapTo, mergeMap, share, startWith, tap } from 'rxjs/operators'; -import { ResourcesService } from '@core/services/resources.service'; -import { getMaterialIcons } from '@shared/models/icon.models'; export interface MaterialIconsDialogData { icon: string; @@ -37,63 +31,16 @@ export interface MaterialIconsDialogData { providers: [], styleUrls: ['./material-icons-dialog.component.scss'] }) -export class MaterialIconsDialogComponent extends DialogComponent - implements OnInit, AfterViewInit { - - @ViewChildren('iconButtons') iconButtons: QueryList; +export class MaterialIconsDialogComponent extends DialogComponent { selectedIcon: string; - icons$: Observable>; - loadingIcons$: Observable; - - showAllControl: UntypedFormControl; constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: MaterialIconsDialogData, - private utils: UtilsService, - private resourcesService: ResourcesService, public dialogRef: MatDialogRef) { super(store, router, dialogRef); this.selectedIcon = data.icon; - this.showAllControl = new UntypedFormControl(false); - } - - ngOnInit(): void { - this.icons$ = this.showAllControl.valueChanges.pipe( - map((showAll) => ({firstTime: false, showAll})), - startWith<{firstTime: boolean; showAll: boolean}>({firstTime: true, showAll: false}), - mergeMap((data) => { - const res = getMaterialIcons(this.resourcesService, data.showAll, ''); - if (data.showAll) { - return res.pipe(delay(100)); - } else { - return data.firstTime ? res : res.pipe(delay(50)); - } - }), - share() - ); - } - - ngAfterViewInit(): void { - this.loadingIcons$ = merge( - this.showAllControl.valueChanges.pipe( - mapTo(true), - ), - this.iconButtons.changes.pipe( - delay(100), - mapTo( false), - ) - ).pipe( - tap((loadingIcons) => { - if (loadingIcons) { - this.showAllControl.disable({emitEvent: false}); - } else { - this.showAllControl.enable({emitEvent: false}); - } - }), - share() - ); } selectIcon(icon: string) { diff --git a/ui-ngx/src/app/shared/components/material-icon-select.component.html b/ui-ngx/src/app/shared/components/material-icon-select.component.html index 5cf7cb6de7..8bae8c3435 100644 --- a/ui-ngx/src/app/shared/components/material-icon-select.component.html +++ b/ui-ngx/src/app/shared/components/material-icon-select.component.html @@ -29,7 +29,13 @@
- {{materialIconFormGroup.get('icon').value}} + diff --git a/ui-ngx/src/app/shared/components/material-icon-select.component.scss b/ui-ngx/src/app/shared/components/material-icon-select.component.scss index 6bfd308ae5..b008fc6838 100644 --- a/ui-ngx/src/app/shared/components/material-icon-select.component.scss +++ b/ui-ngx/src/app/shared/components/material-icon-select.component.scss @@ -22,20 +22,23 @@ border: solid 1px rgba(0, 0, 0, .27); box-sizing: initial; } - &.icon-box { - border: 1px solid rgba(0, 0, 0, 0.12); - border-radius: 4px; - cursor: pointer; - box-sizing: border-box; - padding: 8px; - height: 40px; - width: 40px; - font-size: 22px; - vertical-align: middle; - &.disabled { - cursor: initial; - color: rgba(0, 0, 0, 0.38); - } + } +} + +:host ::ng-deep { + button.mat-mdc-button-base.icon-box { + width: 40px; + min-width: 40px; + height: 40px; + padding: 7px; + &:not(:disabled) { + color: rgba(0, 0, 0, 0.87); + } + > .mat-icon { + width: 24px; + height: 24px; + font-size: 24px; + margin: 0; } } } diff --git a/ui-ngx/src/app/shared/components/material-icon-select.component.ts b/ui-ngx/src/app/shared/components/material-icon-select.component.ts index b8bb7ed25b..740bb4545a 100644 --- a/ui-ngx/src/app/shared/components/material-icon-select.component.ts +++ b/ui-ngx/src/app/shared/components/material-icon-select.component.ts @@ -14,15 +14,18 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { DialogService } from '@core/services/dialog.service'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { TranslateService } from '@ngx-translate/core'; import { coerceBoolean } from '@shared/decorators/coercion'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { MaterialIconsComponent } from '@shared/components/material-icons.component'; +import { MatButton } from '@angular/material/button'; @Component({ selector: 'tb-material-icon-select', @@ -81,6 +84,9 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit constructor(protected store: Store, private dialogs: DialogService, private translate: TranslateService, + private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef, private fb: UntypedFormBuilder, private cd: ChangeDetectorRef) { super(store); @@ -142,6 +148,32 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit } } + openIconPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const materialIconsPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, MaterialIconsComponent, 'left', true, null, + { + selectedIcon: this.materialIconFormGroup.get('icon').value + }, + {}, + {}, {}, true); + materialIconsPopover.tbComponentRef.instance.popover = materialIconsPopover; + materialIconsPopover.tbComponentRef.instance.iconSelected.subscribe((icon) => { + materialIconsPopover.hide(); + this.materialIconFormGroup.patchValue( + {icon}, {emitEvent: true} + ); + this.cd.markForCheck(); + }); + } + } + clear() { this.materialIconFormGroup.get('icon').patchValue(null, {emitEvent: true}); this.cd.markForCheck(); diff --git a/ui-ngx/src/app/shared/components/material-icons.component.html b/ui-ngx/src/app/shared/components/material-icons.component.html new file mode 100644 index 0000000000..39404a9998 --- /dev/null +++ b/ui-ngx/src/app/shared/components/material-icons.component.html @@ -0,0 +1,65 @@ + +
+
icon.icons
+ + search + + + + +
+ + + + +
+
+ + +
+
+
{{ 'icon.no-icons-found' | translate:{iconSearch: searchIconControl.value} }}
+
+
+
diff --git a/ui-ngx/src/app/shared/components/material-icons.component.scss b/ui-ngx/src/app/shared/components/material-icons.component.scss new file mode 100644 index 0000000000..23b959d118 --- /dev/null +++ b/ui-ngx/src/app/shared/components/material-icons.component.scss @@ -0,0 +1,61 @@ +/** + * 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. + */ +.tb-material-icons-panel { + width: 100%; + display: flex; + flex-direction: column; + gap: 16px; + align-items: center; + .tb-material-icons-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-material-icons-title, .tb-material-icons-search, .tb-material-icons-show-more { + width: 100%; + } + .tb-material-icons-viewport { + min-height: 144px; + } + .tb-material-icons-row { + display: flex; + flex-direction: row; + gap: 12px; + } + .tb-material-icons-row + .tb-material-icons-row { + margin-top: 12px; + } + .tb-no-data-available { + min-height: 144px; + } + button.mat-mdc-button-base.tb-select-icon-button { + width: 36px; + min-width: 36px; + height: 36px; + padding: 6px; + &:not(.mat-primary) { + color: rgba(0, 0, 0, 0.54); + } + > .mat-icon { + width: 24px; + height: 24px; + font-size: 24px; + margin: 0; + } + } +} diff --git a/ui-ngx/src/app/shared/components/material-icons.component.ts b/ui-ngx/src/app/shared/components/material-icons.component.ts index 5588540018..9347243af2 100644 --- a/ui-ngx/src/app/shared/components/material-icons.component.ts +++ b/ui-ngx/src/app/shared/components/material-icons.component.ts @@ -1,24 +1,135 @@ +/// +/// 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. +/// + import { PageComponent } from '@shared/components/page.component'; -import { OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnInit, + Output, + ViewChild, + ViewEncapsulation +} from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { UntypedFormControl } from '@angular/forms'; -import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs'; +import { BehaviorSubject, combineLatest, debounce, Observable, of, timer } from 'rxjs'; +import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; +import { getMaterialIcons, MaterialIcon } from '@shared/models/icon.models'; +import { distinctUntilChanged, map, mergeMap, share, startWith, tap } from 'rxjs/operators'; +import { ResourcesService } from '@core/services/resources.service'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { BreakpointObserver } from '@angular/cdk/layout'; +import { MediaBreakpoints } from '@shared/models/constants'; +@Component({ + selector: 'tb-material-icons', + templateUrl: './material-icons.component.html', + providers: [], + styleUrls: ['./material-icons.component.scss'], + encapsulation: ViewEncapsulation.None +}) export class MaterialIconsComponent extends PageComponent implements OnInit { - searchIconsControl: UntypedFormControl; + @ViewChild('iconsPanel') + iconsPanel: CdkVirtualScrollViewport; + + @Input() + selectedIcon: string; + + @Input() + popover: TbPopoverComponent; + + @Output() + iconSelected = new EventEmitter(); + + iconRows$: Observable; showAllSubject = new BehaviorSubject(false); + searchIconControl: UntypedFormControl; + + iconsRowHeight = 48; + + iconsPanelHeight: string; + iconsPanelWidth: string; - icons$: Observable>; + notFound = false; - constructor(protected store: Store) { + constructor(protected store: Store, + private resourcesService: ResourcesService, + private breakpointObserver: BreakpointObserver, + private cd: ChangeDetectorRef) { super(store); - this.searchIconsControl = new UntypedFormControl(''); + this.searchIconControl = new UntypedFormControl(''); } ngOnInit(): void { + const iconsRowSize = this.breakpointObserver.isMatched(MediaBreakpoints['lt-md']) ? 8 : 11; + this.calculatePanelSize(iconsRowSize); + const iconsRowSizeObservable = this.breakpointObserver + .observe(MediaBreakpoints['lt-md']).pipe( + map((state) => state.matches ? 8 : 11), + startWith(iconsRowSize), + ); + this.iconRows$ = combineLatest({showAll: this.showAllSubject.asObservable(), + rowSize: iconsRowSizeObservable, + searchText: this.searchIconControl.valueChanges.pipe( + startWith(''), + debounce((searchText) => searchText ? timer(150) : of({})), + )}).pipe( + map((data) => { + if (data.searchText && !data.showAll) { + data.showAll = true; + this.showAllSubject.next(true); + } + return data; + }), + distinctUntilChanged((p, c) => c.showAll === p.showAll && c.searchText === p.searchText && c.rowSize === p.rowSize), + mergeMap((data) => getMaterialIcons(this.resourcesService, data.rowSize, data.showAll, data.searchText).pipe( + map(iconRows => ({iconRows, iconsRowSize: data.rowSize})) + )), + tap((data) => { + this.notFound = !data.iconRows.length; + this.calculatePanelSize(data.iconsRowSize, data.iconRows.length); + this.cd.markForCheck(); + setTimeout(() => { + this.checkSize(); + }, 0); + }), + map((data) => data.iconRows), + share() + ); + } + + clearSearch() { + this.searchIconControl.patchValue('', {emitEvent: true}); + } + selectIcon(icon: MaterialIcon) { + this.iconSelected.emit(icon.name); } + private calculatePanelSize(iconsRowSize: number, iconRows = 4) { + this.iconsPanelHeight = Math.min(iconRows * this.iconsRowHeight, 10 * this.iconsRowHeight) + 'px'; + this.iconsPanelWidth = (iconsRowSize * 36 + (iconsRowSize - 1) * 12 + 6) + 'px'; + } + + private checkSize() { + this.iconsPanel?.checkViewportSize(); + this.popover?.updatePosition(); + } } diff --git a/ui-ngx/src/app/shared/components/popover.component.ts b/ui-ngx/src/app/shared/components/popover.component.ts index d6d092d03c..91f5a2e902 100644 --- a/ui-ngx/src/app/shared/components/popover.component.ts +++ b/ui-ngx/src/app/shared/components/popover.component.ts @@ -63,8 +63,10 @@ import { coerceBoolean } from '@shared/decorators/coercion'; export type TbPopoverTrigger = 'click' | 'focus' | 'hover' | null; @Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector selector: '[tb-popover]', exportAs: 'tbPopover', + // eslint-disable-next-line @angular-eslint/no-host-metadata-property host: { '[class.tb-popover-open]': 'visible' } @@ -265,12 +267,20 @@ export class TbPopoverDirective implements OnChanges, OnDestroy, AfterViewInit { } else if (delay > 0) { this.delayTimer = setTimeout(() => { this.delayTimer = undefined; - isEnter ? this.show() : this.hide(); + if (isEnter) { + this.show(); + } else { + this.hide(); + } }, delay * 1000); } else { // `isOrigin` is used due to the tooltip will not hide immediately // (may caused by the fade-out animation). - isEnter && isOrigin ? this.show() : this.hide(); + if (isEnter && isOrigin) { + this.show(); + } else { + this.hide(); + } } } @@ -345,15 +355,15 @@ export class TbPopoverDirective implements OnChanges, OnDestroy, AfterViewInit { ` }) -export class TbPopoverComponent implements OnDestroy, OnInit { +export class TbPopoverComponent implements OnDestroy, OnInit { @ViewChild('overlay', { static: false }) overlay!: CdkConnectedOverlay; @ViewChild('popoverRoot', { static: false }) popoverRoot!: ElementRef; @ViewChild('popover', { static: false }) popover!: ElementRef; tbContent: string | TemplateRef | null = null; - tbComponentFactory: ComponentFactory | null = null; - tbComponentRef: ComponentRef | null = null; + tbComponentFactory: ComponentFactory | null = null; + tbComponentRef: ComponentRef | null = null; tbComponentContext: any; tbComponentInjector: Injector | null = null; tbComponentStyle: { [klass: string]: any } = {}; diff --git a/ui-ngx/src/app/shared/components/popover.service.ts b/ui-ngx/src/app/shared/components/popover.service.ts index f547200316..1bef922ec1 100644 --- a/ui-ngx/src/app/shared/components/popover.service.ts +++ b/ui-ngx/src/app/shared/components/popover.service.ts @@ -65,7 +65,7 @@ export class TbPopoverService { displayPopover(trigger: Element, renderer: Renderer2, hostView: ViewContainerRef, componentType: Type, preferredPlacement: PopoverPlacement = 'top', hideOnClickOutside = true, injector?: Injector, context?: any, overlayStyle: any = {}, popoverStyle: any = {}, style?: any, - showCloseButton = true): TbPopoverComponent { + showCloseButton = true): TbPopoverComponent { const componentRef = this.createPopoverRef(hostView); return this.displayPopoverWithComponentRef(componentRef, trigger, renderer, componentType, preferredPlacement, hideOnClickOutside, injector, context, overlayStyle, popoverStyle, style, showCloseButton); @@ -74,7 +74,7 @@ export class TbPopoverService { displayPopoverWithComponentRef(componentRef: ComponentRef, trigger: Element, renderer: Renderer2, componentType: Type, preferredPlacement: PopoverPlacement = 'top', hideOnClickOutside = true, injector?: Injector, context?: any, overlayStyle: any = {}, - popoverStyle: any = {}, style?: any, showCloseButton = true): TbPopoverComponent { + popoverStyle: any = {}, style?: any, showCloseButton = true): TbPopoverComponent { const component = componentRef.instance; this.popoverWithTriggers.push({ trigger, diff --git a/ui-ngx/src/app/shared/components/public-api.ts b/ui-ngx/src/app/shared/components/public-api.ts index 3ed56d0256..04508266e9 100644 --- a/ui-ngx/src/app/shared/components/public-api.ts +++ b/ui-ngx/src/app/shared/components/public-api.ts @@ -26,3 +26,4 @@ export * from './resource/resource-autocomplete.component'; export * from './toggle-header.component'; export * from './toggle-select.component'; export * from './unit-input.component'; +export * from './material-icons.component'; diff --git a/ui-ngx/src/app/shared/models/icon.models.ts b/ui-ngx/src/app/shared/models/icon.models.ts index c774b65ae5..48b643d235 100644 --- a/ui-ngx/src/app/shared/models/icon.models.ts +++ b/ui-ngx/src/app/shared/models/icon.models.ts @@ -1,11 +1,27 @@ -import { Unit, units } from '@shared/models/unit.models'; +/// +/// 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. +/// + import { ResourcesService } from '@core/services/resources.service'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { isEmptyStr, isNotEmptyStr } from '@core/utils'; +import { isNotEmptyStr } from '@core/utils'; export interface MaterialIcon { name: string; + displayName?: string; tags: string[]; } @@ -16,21 +32,41 @@ const searchIconTags = (icon: MaterialIcon, searchText: string): boolean => const searchIcons = (_icons: Array, searchText: string): Array => _icons.filter( i => i.name.toUpperCase().includes(searchText.toUpperCase()) || + i.displayName.toUpperCase().includes(searchText.toUpperCase()) || searchIconTags(i, searchText) ); -const getCommonMaterialIcons = (icons: Array): Array => icons.slice(0, 44); +const getCommonMaterialIcons = (icons: Array, chunkSize: number): Array => icons.slice(0, chunkSize * 4); -export const getMaterialIcons = (resourcesService: ResourcesService, all = false, searchText: string): Observable => - resourcesService.loadJsonResource>('/assets/metadata/material-icons.json').pipe( +export const getMaterialIcons = (resourcesService: ResourcesService, chunkSize = 11, + all = false, searchText: string): Observable => + resourcesService.loadJsonResource>('/assets/metadata/material-icons.json', + (icons) => { + for (const icon of icons) { + const words = icon.name.replace(/_/g, ' ').split(' '); + for (let i = 0; i < words.length; i++) { + words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1); + } + icon.displayName = words.join(' '); + } + return icons; + } + ).pipe( map((icons) => { if (isNotEmptyStr(searchText)) { return searchIcons(icons, searchText); } else if (!all) { - return getCommonMaterialIcons(icons); + return getCommonMaterialIcons(icons, chunkSize); } else { return icons; } }), - map((icons) => icons.map(icon => icon.name)) + map((icons) => { + const iconChunks: MaterialIcon[][] = []; + for (let i = 0; i < icons.length; i += chunkSize) { + const chunk = icons.slice(i, i + chunkSize); + iconChunks.push(chunk); + } + return iconChunks; + }) ); diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index f7c37e4761..25eee9ca08 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -195,6 +195,7 @@ import { ToggleHeaderComponent, ToggleOption } from '@shared/components/toggle-h import { RuleChainSelectComponent } from '@shared/components/rule-chain/rule-chain-select.component'; import { ToggleSelectComponent } from '@shared/components/toggle-select.component'; import { UnitInputComponent } from '@shared/components/unit-input.component'; +import { MaterialIconsComponent } from '@shared/components/material-icons.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -369,6 +370,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) ToggleOption, ToggleSelectComponent, UnitInputComponent, + MaterialIconsComponent, RuleChainSelectComponent ], imports: [ @@ -600,6 +602,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) ToggleOption, ToggleSelectComponent, UnitInputComponent, + MaterialIconsComponent, RuleChainSelectComponent ] }) 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 82f10b7f4c..bc3351aa83 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -68,7 +68,8 @@ "less": "Less", "skip": "Skip", "send": "Send", - "reset": "Reset" + "reset": "Reset", + "show-more": "Show more" }, "aggregation": { "aggregation": "Aggregation", @@ -5501,9 +5502,12 @@ }, "icon": { "icon": "Icon", + "icons": "Icons", "select-icon": "Select icon", "material-icons": "Material icons", - "show-all": "Show all icons" + "show-all": "Show all icons", + "search-icon": "Search icon", + "no-icons-found": "No icons found for '{{iconSearch}}'" }, "phone-input": { "phone-input-label": "Phone number", diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 1f27e59279..8197e32f6c 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -13,6 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +@import './scss/constants'; + .tb-default, .tb-dark { .tb-form-panel { box-shadow: 0 0 10px 6px rgba(11, 17, 51, 0.04); @@ -177,6 +180,13 @@ opacity: 0; } } + &:not(.mat-mdc-form-field-has-icon-prefix) { + .mat-mdc-text-field-wrapper { + &.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) { + padding-left: 12px; + } + } + } &:not(.mat-mdc-form-field-has-icon-suffix) { .mat-mdc-text-field-wrapper { &.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) { @@ -186,7 +196,6 @@ } .mat-mdc-text-field-wrapper { &.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) { - padding-left: 12px; &:not(.mdc-text-field--focused):not(.mdc-text-field--disabled):not(:hover) { .mdc-notched-outline__leading, .mdc-notched-outline__trailing { border-color: rgba(0, 0, 0, 0.12); @@ -203,7 +212,7 @@ line-height: 20px; } } - .mat-mdc-form-field-icon-suffix { + .mat-mdc-form-field-icon-prefix, .mat-mdc-form-field-icon-suffix { height: 40px; font-size: 14px; line-height: 40px; @@ -336,4 +345,49 @@ } } } + + .tb-no-data-available { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + + .tb-no-data-bg { + margin: 10px; + position: relative; + flex: 1; + width: 100%; + max-height: 100px; + &:before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: #305680; + -webkit-mask-image: url(/assets/home/no_data_folder_bg.svg); + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; + -webkit-mask-position: center; + mask-image: url(/assets/home/no_data_folder_bg.svg); + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + } + } + + .tb-no-data-text { + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.54); + @media #{$mat-md-lg} { + font-size: 12px; + line-height: 16px; + } + } } From 25f0c9e15f7d04127a640a2d765b16eeb9d1621a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 13 Jul 2023 12:48:21 +0300 Subject: [PATCH 066/168] UI: Minor improvements --- ui-ngx/src/app/core/services/utils.service.ts | 26 +++++++++++- .../lib/alarms-table-widget.component.ts | 40 ++++--------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 339c1e1742..a6065dfe62 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -41,7 +41,7 @@ import { TranslateService } from '@ngx-translate/core'; import { customTranslationsPrefix, i18nPrefix } from '@app/shared/models/constants'; import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models'; import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models'; -import { alarmFields } from '@shared/models/alarm.models'; +import { alarmFields, alarmSeverityTranslations, alarmStatusTranslations } from '@shared/models/alarm.models'; import { materialColors } from '@app/shared/models/material.models'; import { WidgetInfo } from '@home/models/widget-component.models'; import jsonSchemaDefaults from 'json-schema-defaults'; @@ -55,6 +55,8 @@ import { TelemetryType } from '@shared/models/telemetry/telemetry.models'; import { EntityId } from '@shared/models/id/entity-id'; +import { DatePipe } from '@angular/common'; +import { entityTypeTranslations } from '@shared/models/entity-type.models'; const i18nRegExp = new RegExp(`{${i18nPrefix}:[^{}]+}`, 'g'); @@ -115,6 +117,7 @@ export class UtilsService { constructor(@Inject(WINDOW) private window: Window, private zone: NgZone, + private datePipe: DatePipe, private translate: TranslateService) { let frame: Element = null; try { @@ -170,6 +173,27 @@ export class UtilsService { return deepClone(this.defaultAlarmDataKeys); } + public defaultAlarmFieldContent(key: DataKey | {name: string}, value: any): string { + if (isDefined(value)) { + const alarmField = alarmFields[key.name]; + if (alarmField) { + if (alarmField.time) { + return value ? this.datePipe.transform(value, 'yyyy-MM-dd HH:mm:ss') : ''; + } else if (alarmField === alarmFields.severity) { + return this.translate.instant(alarmSeverityTranslations.get(value)); + } else if (alarmField === alarmFields.status) { + return alarmStatusTranslations.get(value) ? this.translate.instant(alarmStatusTranslations.get(value)) : value; + } else if (alarmField === alarmFields.originatorType) { + return this.translate.instant(entityTypeTranslations.get(value).type); + } else if (alarmField.value === alarmFields.assignee.value) { + return ''; + } + } + return value; + } + return ''; + } + public generateObjectFromJsonSchema(schema: any): any { const obj = jsonSchemaDefaults(schema); deleteNullProperties(obj); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index a0062645ac..db2d04ea88 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -23,6 +23,7 @@ import { Injector, Input, NgZone, + OnDestroy, OnInit, StaticProvider, ViewChild, @@ -52,7 +53,6 @@ import { Direction } from '@shared/models/page/sort-order'; import { CollectionViewer, DataSource, SelectionModel } from '@angular/cdk/collections'; import { BehaviorSubject, forkJoin, fromEvent, merge, Observable, Subscription } from 'rxjs'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; -import { entityTypeTranslations } from '@shared/models/entity-type.models'; import { debounceTime, distinctUntilChanged, map, take, tap } from 'rxjs/operators'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort, SortDirection } from '@angular/material/sort'; @@ -92,15 +92,7 @@ import { DisplayColumnsPanelComponent, DisplayColumnsPanelData } from '@home/components/widget/lib/display-columns-panel.component'; -import { - AlarmDataInfo, - alarmFields, - AlarmInfo, - alarmSeverityColors, - alarmSeverityTranslations, - AlarmStatus, - alarmStatusTranslations -} from '@shared/models/alarm.models'; +import { AlarmDataInfo, alarmFields, AlarmInfo, alarmSeverityColors, AlarmStatus } from '@shared/models/alarm.models'; import { DatePipe } from '@angular/common'; import { AlarmDetailsDialogComponent, @@ -139,7 +131,6 @@ import { AlarmFilterConfigData } from '@home/components/alarm/alarm-filter-config.component'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; -import { UserId } from '@shared/models/id/user-id'; interface AlarmsTableWidgetSettings extends TableWidgetSettings { alarmsTitle: string; @@ -167,7 +158,7 @@ interface AlarmWidgetActionDescriptor extends TableCellButtonActionDescriptor { templateUrl: './alarms-table-widget.component.html', styleUrls: ['./alarms-table-widget.component.scss', './table-widget.scss'] }) -export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, AfterViewInit { +export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, OnDestroy, AfterViewInit { @Input() ctx: WidgetContext; @@ -431,7 +422,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, keySettings.columnWidth = '120px'; } if (alarmField && alarmField.keyName === alarmFields.assignee.keyName) { - keySettings.columnWidth = '120px' + keySettings.columnWidth = '120px'; } } this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings, 'value, alarm, ctx'); @@ -543,14 +534,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, overlayRef.dispose(); }); - const columns: DisplayColumn[] = this.columns.map(column => { - return { + const columns: DisplayColumn[] = this.columns.map(column => ({ title: column.title, def: column.def, display: this.displayedColumns.indexOf(column.def) > -1, selectable: this.columnSelectionAvailability[column.def] - }; - }); + })); const providers: StaticProvider[] = [ { @@ -1010,7 +999,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, data: { alarmId: alarm.id.id } - }).afterClosed() + }).afterClosed(); } } @@ -1018,20 +1007,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, if (isDefined(value)) { const alarmField = alarmFields[key.name]; if (alarmField) { - if (alarmField.time) { - return value ? this.datePipe.transform(value, 'yyyy-MM-dd HH:mm:ss') : ''; - } else if (alarmField.value === alarmFields.severity.value) { - return this.translate.instant(alarmSeverityTranslations.get(value)); - } else if (alarmField.value === alarmFields.status.value) { - return alarmStatusTranslations.get(value) ? this.translate.instant(alarmStatusTranslations.get(value)) : value; - } else if (alarmField.value === alarmFields.originatorType.value) { - return this.translate.instant(entityTypeTranslations.get(value).type); - } else if (alarmField.value === alarmFields.assignee.value) { - return ''; - } - else { - return value; - } + return this.utils.defaultAlarmFieldContent(key, value); } const entityField = entityFields[key.name]; if (entityField) { From b74e52d14ad730c1d752eab3e5439567aa3dca39 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 13 Jul 2023 18:14:50 +0300 Subject: [PATCH 067/168] UI: Improve color picker --- .../src/app/core/services/dialog.service.ts | 3 +- .../alarm/alarm-assignee.component.scss | 6 -- .../alarms-table-basic-config.component.html | 27 ++++----- ...entities-table-basic-config.component.html | 27 ++++----- .../simple-card-basic-config.component.html | 22 +++----- ...meseries-table-basic-config.component.html | 27 ++++----- .../chart/flot-basic-config.component.html | 27 ++++----- .../config/data-key-config.component.html | 11 ++-- .../widget/config/data-keys.component.html | 2 +- .../widget/config/data-keys.component.ts | 32 ++++++++--- .../chart/flot-key-settings.component.html | 11 ++-- .../flot-latest-key-settings.component.html | 11 ++-- .../chart/flot-widget-settings.component.html | 54 +++++++----------- .../widget/widget-config.component.html | 27 ++++----- .../components/color-input.component.html | 13 ++++- .../components/color-input.component.scss | 9 +++ .../components/color-input.component.ts | 42 +++++++++++++- .../color-picker-panel.component.html | 30 ++++++++++ .../color-picker-panel.component.scss | 36 ++++++++++++ .../color-picker-panel.component.ts | 55 +++++++++++++++++++ .../color-picker/color-picker.component.html | 14 ++++- .../color-picker/color-picker.component.scss | 39 ++++++++----- .../color-picker/color-picker.component.ts | 27 +++++---- .../dialog/color-picker-dialog.component.html | 30 ++++------ .../dialog/color-picker-dialog.component.scss | 22 ++++++++ .../dialog/color-picker-dialog.component.ts | 24 +++----- ui-ngx/src/app/shared/shared.module.ts | 3 + .../assets/locale/locale.constant-en_US.json | 3 + ui-ngx/src/form.scss | 53 +++++++++--------- 29 files changed, 423 insertions(+), 264 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.html create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.scss create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.ts create mode 100644 ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.scss diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts index 1daf90ac82..2ca2d6d87e 100644 --- a/ui-ngx/src/app/core/services/dialog.service.ts +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -103,7 +103,8 @@ export class DialogService { panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { color - } + }, + autoFocus: false }).afterClosed(); } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss index 947eeb01d0..3aed0ccb5e 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss @@ -45,9 +45,3 @@ margin-right: 8px; } } - -.drop-down-icon { - &.inline { - margin-right: -12px; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html index 90314b9cbe..18cd34609e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html @@ -58,16 +58,15 @@
-
+
{{ 'widget-config.card-icon' | translate }} -
+
- @@ -82,23 +81,17 @@ {{ 'fullscreen.fullscreen' | translate }}
-
+
{{ 'widget-config.text-color' | translate }}
-
- - - -
+ +
-
+
{{ 'widget-config.background-color' | translate }}
-
- - - -
+ +
-
+
{{ 'widget-config.card-icon' | translate }} -
+
- @@ -70,23 +69,17 @@ {{ 'fullscreen.fullscreen' | translate }}
-
+
{{ 'widget-config.text-color' | translate }}
-
- - - -
+ +
-
+
{{ 'widget-config.background-color' | translate }}
-
- - - -
+ +
{{ 'fullscreen.fullscreen' | translate }}
-
+
{{ 'widget-config.text-color' | translate }}
-
- - - -
+ +
-
+
{{ 'widget-config.background' | translate }}
-
- - - -
+ +
-
+
{{ 'widget-config.card-icon' | translate }} -
+
- @@ -70,23 +69,17 @@ {{ 'fullscreen.fullscreen' | translate }}
-
+
{{ 'widget-config.text-color' | translate }}
-
- - - -
+ +
-
+
{{ 'widget-config.background-color' | translate }}
-
- - - -
+ +
-
+
{{ 'widget-config.card-icon' | translate }} -
+
- @@ -68,23 +67,17 @@ {{ 'fullscreen.fullscreen' | translate }}
-
+
{{ 'widget-config.text-color' | translate }}
-
- - - -
+ +
-
+
{{ 'widget-config.background-color' | translate }}
-
- - - -
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html index a32735e0d1..8510e19f24 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html @@ -59,14 +59,11 @@
-
+
{{ 'datakey.color' | translate }}
-
- - - -
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html index 39fffd79e7..65daa6552c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html @@ -72,7 +72,7 @@
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts index 95fe5bc444..02060db364 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts @@ -27,6 +27,7 @@ import { SimpleChanges, SkipSelf, ViewChild, + ViewContainerRef, ViewEncapsulation } from '@angular/core'; import { @@ -56,7 +57,6 @@ import { alarmFields } from '@shared/models/alarm.models'; import { UtilsService } from '@core/services/utils.service'; import { ErrorStateMatcher } from '@angular/material/core'; import { TruncatePipe } from '@shared/pipe/truncate.pipe'; -import { DialogService } from '@core/services/dialog.service'; import { MatDialog } from '@angular/material/dialog'; import { DataKeyConfigDialogComponent, @@ -69,6 +69,8 @@ import { DndDropEvent } from 'ngx-drag-drop/lib/dnd-dropzone.directive'; import { moveItemInArray } from '@angular/cdk/drag-drop'; import { coerceBoolean } from '@shared/decorators/coercion'; import { DatasourceComponent } from '@home/components/widget/config/datasource.component'; +import { ColorPickerPanelComponent } from '@shared/components/color-picker/color-picker-panel.component'; +import { TbPopoverService } from '@shared/components/popover.service'; @Component({ selector: 'tb-data-keys', @@ -208,10 +210,11 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange private datasourceComponent: DatasourceComponent, public translate: TranslateService, private utils: UtilsService, - private dialogs: DialogService, private dialog: MatDialog, private fb: UntypedFormBuilder, private cd: ChangeDetectorRef, + private popoverService: TbPopoverService, + private viewContainerRef: ViewContainerRef, private renderer: Renderer2, public truncate: TruncatePipe) { } @@ -471,15 +474,30 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange this.propagateChange(this.modelValue); } - showColorPicker(key: DataKey) { - this.dialogs.colorPicker(key.color).subscribe( - (color) => { + openColorPickerPopup(key: DataKey, $event: Event, keyColorButton: HTMLDivElement) { + if ($event) { + $event.stopPropagation(); + } + const trigger = keyColorButton; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const colorPickerPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, ColorPickerPanelComponent, 'left', true, null, + { + color: key.color + }, + {}, + {}, {}, true); + colorPickerPopover.tbComponentRef.instance.popover = colorPickerPopover; + colorPickerPopover.tbComponentRef.instance.colorSelected.subscribe((color) => { + colorPickerPopover.hide(); if (color && key.color !== color) { key.color = color; this.propagateChange(this.modelValue); } - } - ); + }); + } } editDataKey(key: DataKey, index: number) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html index f3176f9834..96ff4d8559 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html @@ -235,14 +235,11 @@
-
+
{{ 'widgets.chart.comparison-line-color' | translate }}
-
- - - -
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-latest-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-latest-key-settings.component.html index cdff37f492..286d26481b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-latest-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-latest-key-settings.component.html @@ -36,14 +36,11 @@ px
-
+
{{ 'widgets.chart.threshold-color' | translate }}
-
- - - -
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html index defc26856a..5dd7eaa2dc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html @@ -61,14 +61,13 @@
-
+
{{ 'widgets.chart.default-font' | translate }}
-
+
px - @@ -132,14 +131,11 @@ -
+
{{ 'widget-config.color' | translate }}
-
- - - -
+ +
widget-config.decimals-short
@@ -187,14 +183,11 @@ -
+
{{ 'widget-config.color' | translate }}
-
- - - -
+ +
@@ -213,36 +206,29 @@ {{ 'widgets.chart.horizontal-grid-lines' | translate }}
-
+
{{ 'widgets.chart.grid-lines-color' | translate }}
-
- - - -
+ +
-
+
{{ 'widgets.chart.border' | translate }}
-
+
px -
-
+
{{ 'widgets.chart.background-color' | translate }}
-
- - - -
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html index ab226987ca..68a9ed6f3d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html @@ -48,11 +48,11 @@
-
+
{{ 'widget-config.display-icon' | translate }} -
+
@@ -60,7 +60,6 @@ - @@ -84,23 +83,17 @@
widget-config.card-style
-
+
{{ 'widget-config.text-color' | translate }}
-
- - - -
+ +
-
+
{{ 'widget-config.background-color' | translate }}
-
- - - -
+ +
{{ 'widget-config.padding' | translate }}
diff --git a/ui-ngx/src/app/shared/components/color-input.component.html b/ui-ngx/src/app/shared/components/color-input.component.html index 2a283f7d93..c027104c6e 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.html +++ b/ui-ngx/src/app/shared/components/color-input.component.html @@ -35,7 +35,14 @@ -
-
-
+
diff --git a/ui-ngx/src/app/shared/components/color-input.component.scss b/ui-ngx/src/app/shared/components/color-input.component.scss index ca8ffedc72..b81ac0fdf8 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.scss +++ b/ui-ngx/src/app/shared/components/color-input.component.scss @@ -13,6 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +@import './../../../scss/mixins'; + :host { .mat-mdc-form-field { width: 100%; @@ -29,4 +32,10 @@ margin: 0; } } + button.mat-mdc-button-base.color-box { + width: 40px; + min-width: 40px; + height: 40px; + padding: 7px; + } } diff --git a/ui-ngx/src/app/shared/components/color-input.component.ts b/ui-ngx/src/app/shared/components/color-input.component.ts index fa6c73116e..f22b91fde2 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.ts +++ b/ui-ngx/src/app/shared/components/color-input.component.ts @@ -14,15 +14,24 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + Validators +} from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DialogService } from '@core/services/dialog.service'; import { coerceBoolean } from '@shared/decorators/coercion'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { ColorPickerPanelComponent } from '@shared/components/color-picker/color-picker-panel.component'; +import { MatButton } from '@angular/material/button'; @Component({ selector: 'tb-color-input', @@ -100,6 +109,9 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro constructor(protected store: Store, private dialogs: DialogService, private translate: TranslateService, + private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef, private fb: UntypedFormBuilder, private cd: ChangeDetectorRef) { super(store); @@ -167,6 +179,32 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro ); } + openColorPickerPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const colorPickerPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, ColorPickerPanelComponent, 'left', true, null, + { + color: this.colorFormGroup.get('color').value + }, + {}, + {}, {}, true); + colorPickerPopover.tbComponentRef.instance.popover = colorPickerPopover; + colorPickerPopover.tbComponentRef.instance.colorSelected.subscribe((color) => { + colorPickerPopover.hide(); + this.colorFormGroup.patchValue( + {color}, {emitEvent: true} + ); + this.cd.markForCheck(); + }); + } + } + clear() { this.colorFormGroup.get('color').patchValue(null, {emitEvent: true}); this.cd.markForCheck(); diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.html b/ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.html new file mode 100644 index 0000000000..56e4523b67 --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.html @@ -0,0 +1,30 @@ + +
+
color.color
+ +
+ +
+
diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.scss b/ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.scss new file mode 100644 index 0000000000..e7d78b4018 --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.scss @@ -0,0 +1,36 @@ +/** + * 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. + */ +.tb-color-picker-panel { + width: 328px; + display: flex; + flex-direction: column; + gap: 16px; + .tb-color-picker-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-color-picker-panel-buttons { + height: 60px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.ts b/ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.ts new file mode 100644 index 0000000000..59199388b8 --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker-panel.component.ts @@ -0,0 +1,55 @@ +/// +/// 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. +/// + +import { PageComponent } from '@shared/components/page.component'; +import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { UntypedFormControl } from '@angular/forms'; +import { TbPopoverComponent } from '@shared/components/popover.component'; + +@Component({ + selector: 'tb-color-picker-panel', + templateUrl: './color-picker-panel.component.html', + providers: [], + styleUrls: ['./color-picker-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class ColorPickerPanelComponent extends PageComponent implements OnInit { + + @Input() + color: string; + + @Input() + popover: TbPopoverComponent; + + @Output() + colorSelected = new EventEmitter(); + + colorPickerControl: UntypedFormControl; + + constructor(protected store: Store) { + super(store); + } + + ngOnInit(): void { + this.colorPickerControl = new UntypedFormControl(this.color); + } + + selectColor() { + this.colorSelected.emit(this.colorPickerControl.value); + } +} diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html index d1432d27f5..5806037c89 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html @@ -19,7 +19,7 @@
@@ -29,7 +29,12 @@
-
+ + HEX + RGBA + HSLA + +
-
+
+ +
+
diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss index ea2bfa94ba..dc1d3b5042 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss @@ -17,12 +17,10 @@ width: 100%; display: flex; flex-direction: column; - gap: 16px; + gap: 32px; .saturation-component { - height: 100%; - min-height: 200px; - max-height: 300px; + height: 238px; border-radius: 8px; } @@ -55,6 +53,12 @@ .color-input-block { display: flex; + gap: 20px; + + .presentation-select { + font-size: 14px; + width: 56px; + } .color-input { flex: 1; @@ -63,16 +67,13 @@ color: initial; } } + } - .type-btn { - height: 26px; - width: 20px; - background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAgCAMAAAAootjDAAAAM1BMVEUAAAAzMzMzMzMzMzMzMzM0NDQzMzMzMzM0NDQzMzMzMzM0NDQzMzMzMzMyMjIrKyszMzPF8UZlAAAAEHRSTlMA1fHr4ZxxSRP45sG+sCkGH2+Z6QAAAHJJREFUKM+9kkkSgCAQA0FEVLb5/2tViqgQvNrHviSzKGCt6nDGuNass8i8NsrLiX+bZbrUtDwm7VLYE0zWUtEZ+RvUZpEvN8YhH9QmQRoC8kFpEnVHVP/DJUZVeSAem5fDKxwtms/BR+PT8gN8vwk/0wE1gQzNVYryIwAAAABJRU5ErkJggg==') no-repeat center; - background-size: 6px 12px; - - &:hover { - background-color: #eee; - } + .color-presets-block { + .color-presets-component { + display: flex; + flex-direction: column; + gap: 12px; } } } @@ -105,4 +106,16 @@ } } } + + .color-presets-component { + .presets-row { + gap: 10px; + justify-content: space-between; + } + color-preset { + height: 20px; + width: 20px; + border-radius: 4px; + } + } } diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts index ce49310b03..47a745819d 100644 --- a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts @@ -17,7 +17,7 @@ import { Component, forwardRef, OnDestroy } from '@angular/core'; import { Color, ColorPickerControl } from '@iplab/ngx-color-picker'; import { Subscription } from 'rxjs'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms'; export enum ColorType { hex = 'hex', @@ -29,6 +29,10 @@ export enum ColorType { cmyk = 'cmyk' } +const colorPresetsHex = + ['#435B63', '#F44336', '#E89623', '#F5DD00', '#8BC34A', '#4CAF50', '#009688', '#048AD3', '#673AB7', '#9C27B0', '#E91E63', + '#A1ADB1', '#F9A19B', '#FFD190', '#FFF59D', '#C5E1A4', '#A5D7A7', '#80CBC3', '#81C4E9', '#B39CDB', '#CD93D7', '#F48FB1']; + @Component({ selector: `tb-color-picker`, templateUrl: `./color-picker.component.html`, @@ -43,10 +47,13 @@ export enum ColorType { }) export class ColorPickerComponent implements ControlValueAccessor, OnDestroy { - selectedPresentation = 0; presentations = [ColorType.hex, ColorType.rgba, ColorType.hsla]; control = new ColorPickerControl(); + presentationControl = new UntypedFormControl(0); + + colorPresets: Color[] = colorPresetsHex.map(c => Color.from(c)); + private modelValue: string; private subscriptions: Array = []; @@ -65,6 +72,11 @@ export class ColorPickerComponent implements ControlValueAccessor, OnDestroy { } }) ); + this.subscriptions.push( + this.presentationControl.valueChanges.subscribe(() => { + this.updateModel(); + }) + ); } registerOnChange(fn: any): void { @@ -86,12 +98,11 @@ export class ColorPickerComponent implements ControlValueAccessor, OnDestroy { } else if (this.control.initType === ColorType.hsl) { this.control.initType = ColorType.hsla; } - - this.selectedPresentation = this.presentations.indexOf(this.control.initType); + this.presentationControl.patchValue(this.presentations.indexOf(this.control.initType), {emitEvent: false}); } private updateModel() { - const color: string = this.getValueByType(this.control.value, this.presentations[this.selectedPresentation]); + const color: string = this.getValueByType(this.control.value, this.presentations[this.presentationControl.value]); if (this.modelValue !== color) { this.modelValue = color; this.propagateChange(color); @@ -103,12 +114,6 @@ export class ColorPickerComponent implements ControlValueAccessor, OnDestroy { this.subscriptions.length = 0; } - public changePresentation(): void { - this.selectedPresentation = - this.selectedPresentation === this.presentations.length - 1 ? 0 : this.selectedPresentation + 1; - this.updateModel(); - } - getValueByType(color: Color, type: ColorType): string { switch (type) { case ColorType.hex: diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html index eaec4c0b5e..0d916f428a 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html @@ -15,22 +15,14 @@ limitations under the License. --> -
-
- -
-
- - - -
-
+
+ + + +
diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.scss b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.scss new file mode 100644 index 0000000000..afd8d1cfcb --- /dev/null +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.scss @@ -0,0 +1,22 @@ +/** + * 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. + */ +:host { + .tb-close-button { + position: absolute; + top: 6px; + right: 6px; + } +} diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts index 44959023f4..219ed0ec56 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts @@ -14,11 +14,10 @@ /// limitations under the License. /// -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@shared/components/dialog.component'; @@ -29,33 +28,26 @@ export interface ColorPickerDialogData { @Component({ selector: 'tb-color-picker-dialog', templateUrl: './color-picker-dialog.component.html', - styleUrls: [] + styleUrls: ['./color-picker-dialog.component.scss'] }) -export class ColorPickerDialogComponent extends DialogComponent - implements OnInit { +export class ColorPickerDialogComponent extends DialogComponent { - colorPickerFormGroup: FormGroup; + color: string; constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: ColorPickerDialogData, - public dialogRef: MatDialogRef, - public fb: FormBuilder) { + public dialogRef: MatDialogRef) { super(store, router, dialogRef); + this.color = data.color; } - ngOnInit(): void { - this.colorPickerFormGroup = this.fb.group({ - color: [this.data.color, [Validators.required]] - }); + selectColor(color: string) { + this.dialogRef.close(color); } cancel(): void { this.dialogRef.close(null); } - select(): void { - const color: string = this.colorPickerFormGroup.get('color').value; - this.dialogRef.close(color); - } } diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 25eee9ca08..11c4b4effb 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -196,6 +196,7 @@ import { RuleChainSelectComponent } from '@shared/components/rule-chain/rule-cha import { ToggleSelectComponent } from '@shared/components/toggle-select.component'; import { UnitInputComponent } from '@shared/components/unit-input.component'; import { MaterialIconsComponent } from '@shared/components/material-icons.component'; +import { ColorPickerPanelComponent } from '@shared/components/color-picker/color-picker-panel.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -365,6 +366,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) GtMdLgLayoutGapDirective, GtMdLgShowHideDirective, ColorPickerComponent, + ColorPickerPanelComponent, ResourceAutocompleteComponent, ToggleHeaderComponent, ToggleOption, @@ -597,6 +599,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) GtMdLgLayoutGapDirective, GtMdLgShowHideDirective, ColorPickerComponent, + ColorPickerPanelComponent, ResourceAutocompleteComponent, ToggleHeaderComponent, ToggleOption, 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 bc3351aa83..cd379fa4dc 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5500,6 +5500,9 @@ } } }, + "color": { + "color": "Color" + }, "icon": { "icon": "Icon", "icons": "Icons", diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 8197e32f6c..c9e3bc6a16 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -131,14 +131,11 @@ } .tb-form-row { height: 100%; - padding-top: 7px; - padding-bottom: 7px; display: flex; flex-direction: row; align-items: center; gap: 16px; - padding-left: 16px; - padding-right: 12px; + padding: 7px 7px 7px 16px; border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 6px; &.same-padding { @@ -314,33 +311,33 @@ .tb-prompt { height: 38px; } + } - .tb-form-table-row { - height: 38px; - display: flex; - flex-direction: row; - gap: 12px; - padding-left: 12px; + .tb-form-table-row { + height: 38px; + display: flex; + flex-direction: row; + gap: 12px; + padding-left: 12px; - &.tb-draggable { - gap: 0; - padding-left: 0; - background: #fff; - } + &.tb-draggable { + gap: 0; + padding-left: 0; + background: #fff; + } - &-cell-buttons { - display: flex; - flex-direction: row; - button.mat-mdc-icon-button.mat-mdc-button-base { - padding: 7px; - width: 38px; - height: 38px; - .mat-icon { - color: rgba(0, 0, 0, 0.38); - } - &.tb-hidden { - visibility: hidden; - } + &-cell-buttons { + display: flex; + flex-direction: row; + button.mat-mdc-icon-button.mat-mdc-button-base { + padding: 7px; + width: 38px; + height: 38px; + .mat-icon { + color: rgba(0, 0, 0, 0.38); + } + &.tb-hidden { + visibility: hidden; } } } From a9b8a6459e449beb995f0d16d15f31145331988b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 13 Jul 2023 20:04:25 +0300 Subject: [PATCH 068/168] UI: Load home dashboards from asset resources --- .../home-links/home-links-routing.module.ts | 43 ++++++++++--------- .../dashboard/customer_user_home_page.json} | 0 .../dashboard/sys_admin_home_page.json} | 0 .../dashboard/tenant_admin_home_page.json} | 0 4 files changed, 22 insertions(+), 21 deletions(-) rename ui-ngx/src/{app/modules/home/pages/home-links/customer_user_home_page.raw => assets/dashboard/customer_user_home_page.json} (100%) rename ui-ngx/src/{app/modules/home/pages/home-links/sys_admin_home_page.raw => assets/dashboard/sys_admin_home_page.json} (100%) rename ui-ngx/src/{app/modules/home/pages/home-links/tenant_admin_home_page.raw => assets/dashboard/tenant_admin_home_page.json} (100%) diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts b/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts index 5edc9cb7fc..c1c1c9bc9e 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts @@ -25,20 +25,19 @@ import { DashboardService } from '@core/http/dashboard.service'; import { select, Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { map } from 'rxjs/operators'; -import { - getCurrentAuthUser, - selectHasRepository, - selectPersistDeviceStateToTelemetry -} from '@core/auth/auth.selectors'; -import sysAdminHomePageDashboardJson from '!raw-loader!./sys_admin_home_page.raw'; -import tenantAdminHomePageDashboardJson from '!raw-loader!./tenant_admin_home_page.raw'; -import customerUserHomePageDashboardJson from '!raw-loader!./customer_user_home_page.raw'; +import { getCurrentAuthUser, selectPersistDeviceStateToTelemetry } from '@core/auth/auth.selectors'; import { EntityKeyType } from '@shared/models/query/query.models'; +import { ResourcesService } from '@core/services/resources.service'; + +const sysAdminHomePageJson = '/assets/dashboard/sys_admin_home_page.json'; +const tenantAdminHomePageJson = '/assets/dashboard/tenant_admin_home_page.json'; +const customerUserHomePageJson = '/assets/dashboard/customer_user_home_page.json'; @Injectable() export class HomeDashboardResolver implements Resolve { constructor(private dashboardService: DashboardService, + private resourcesService: ResourcesService, private store: Store) { } @@ -50,13 +49,13 @@ export class HomeDashboardResolver implements Resolve { const authority = getCurrentAuthUser(this.store).authority; switch (authority) { case Authority.SYS_ADMIN: - dashboard$ = of(JSON.parse(sysAdminHomePageDashboardJson)); + dashboard$ = this.resourcesService.loadJsonResource(sysAdminHomePageJson); break; case Authority.TENANT_ADMIN: - dashboard$ = this.updateDeviceActivityKeyFilterIfNeeded(JSON.parse(tenantAdminHomePageDashboardJson)); + dashboard$ = this.updateDeviceActivityKeyFilterIfNeeded(this.resourcesService.loadJsonResource(tenantAdminHomePageJson)); break; case Authority.CUSTOMER_USER: - dashboard$ = this.updateDeviceActivityKeyFilterIfNeeded(JSON.parse(customerUserHomePageDashboardJson)); + dashboard$ = this.updateDeviceActivityKeyFilterIfNeeded(this.resourcesService.loadJsonResource(customerUserHomePageJson)); break; } if (dashboard$) { @@ -73,18 +72,20 @@ export class HomeDashboardResolver implements Resolve { ); } - private updateDeviceActivityKeyFilterIfNeeded(dashboard: HomeDashboard): Observable { + private updateDeviceActivityKeyFilterIfNeeded(dashboard$: Observable): Observable { return this.store.pipe(select(selectPersistDeviceStateToTelemetry)).pipe( - map((persistToTelemetry) => { - if (persistToTelemetry) { - for (const filterId of Object.keys(dashboard.configuration.filters)) { - if (['Active Devices', 'Inactive Devices'].includes(dashboard.configuration.filters[filterId].filter)) { - dashboard.configuration.filters[filterId].keyFilters[0].key.type = EntityKeyType.TIME_SERIES; + mergeMap((persistToTelemetry) => dashboard$.pipe( + map((dashboard) => { + if (persistToTelemetry) { + for (const filterId of Object.keys(dashboard.configuration.filters)) { + if (['Active Devices', 'Inactive Devices'].includes(dashboard.configuration.filters[filterId].filter)) { + dashboard.configuration.filters[filterId].keyFilters[0].key.type = EntityKeyType.TIME_SERIES; + } + } } - } - } - return dashboard; - }) + return dashboard; + }) + )) ); } } diff --git a/ui-ngx/src/app/modules/home/pages/home-links/customer_user_home_page.raw b/ui-ngx/src/assets/dashboard/customer_user_home_page.json similarity index 100% rename from ui-ngx/src/app/modules/home/pages/home-links/customer_user_home_page.raw rename to ui-ngx/src/assets/dashboard/customer_user_home_page.json diff --git a/ui-ngx/src/app/modules/home/pages/home-links/sys_admin_home_page.raw b/ui-ngx/src/assets/dashboard/sys_admin_home_page.json similarity index 100% rename from ui-ngx/src/app/modules/home/pages/home-links/sys_admin_home_page.raw rename to ui-ngx/src/assets/dashboard/sys_admin_home_page.json diff --git a/ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json similarity index 100% rename from ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw rename to ui-ngx/src/assets/dashboard/tenant_admin_home_page.json From 5eebbf89859ee08ee6c481646c060495f51086ce Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 13 Jul 2023 22:28:59 +0200 Subject: [PATCH 069/168] web socket handler tests added. ws msg queue fixed the last msg pickup (and msg order as result) --- .../controller/plugin/TbWebSocketHandler.java | 44 +++-- .../plugin/TbWebSocketHandlerTest.java | 160 ++++++++++++++++++ 2 files changed, 186 insertions(+), 18 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java 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 56d88143a5..481d412d59 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 @@ -219,12 +219,12 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke .build(); } - private class SessionMetaData implements SendHandler { + class SessionMetaData implements SendHandler { private final WebSocketSession session; private final RemoteEndpoint.Async asyncRemote; private final WebSocketSessionRef sessionRef; - private final AtomicBoolean isSending = new AtomicBoolean(false); + final AtomicBoolean isSending = new AtomicBoolean(false); private final Queue> msgQueue; private volatile long lastActivityTime; @@ -254,11 +254,13 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } } - private void closeSession(CloseStatus reason) { + void closeSession(CloseStatus reason) { try { close(this.sessionRef, reason); } catch (IOException ioe) { log.trace("[{}] Session transport error", session.getId(), ioe); + } finally { + msgQueue.clear(); } } @@ -271,20 +273,19 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } void sendMsg(TbWebSocketMsg msg) { - if (isSending.compareAndSet(false, true)) { - sendMsgInternal(msg); - } else { - try { - msgQueue.add(msg); - } catch (RuntimeException e) { - if (log.isTraceEnabled()) { - log.trace("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId(), e); - } else { - log.info("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId()); - } - closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!")); + try { + msgQueue.add(msg); + } catch (RuntimeException e) { + if (log.isTraceEnabled()) { + log.trace("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId(), e); + } else { + log.info("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId()); } + closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!")); + return; } + + processNextMsg(); } private void sendMsgInternal(TbWebSocketMsg msg) { @@ -292,9 +293,11 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke if (TbWebSocketMsgType.TEXT.equals(msg.getType())) { TbWebSocketTextMsg textMsg = (TbWebSocketTextMsg) msg; this.asyncRemote.sendText(textMsg.getMsg(), this); + // isSending status will be reset in the onResult method by call back } else { TbWebSocketPingMsg pingMsg = (TbWebSocketPingMsg) msg; - this.asyncRemote.sendPing(pingMsg.getMsg()); + this.asyncRemote.sendPing(pingMsg.getMsg()); // blocking call + isSending.set(false); processNextMsg(); } } catch (Exception e) { @@ -308,12 +311,17 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke if (!result.isOK()) { log.trace("[{}] Failed to send msg", session.getId(), result.getException()); closeSession(CloseStatus.SESSION_NOT_RELIABLE); - } else { - processNextMsg(); + return; } + + isSending.set(false); + processNextMsg(); } private void processNextMsg() { + if (msgQueue.isEmpty() || !isSending.compareAndSet(false, true)) { + return; + } TbWebSocketMsg msg = msgQueue.poll(); if (msg != null) { sendMsgInternal(msg); diff --git a/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java b/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java new file mode 100644 index 0000000000..0394e8a505 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java @@ -0,0 +1,160 @@ +/** + * 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.controller.plugin; + +import lombok.extern.slf4j.Slf4j; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.adapter.NativeWebSocketSession; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.service.ws.WebSocketSessionRef; + +import javax.websocket.RemoteEndpoint; +import javax.websocket.SendHandler; +import javax.websocket.SendResult; +import javax.websocket.Session; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +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 java.util.stream.Collectors; +import java.util.stream.IntStream; + +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.BDDMockito.willAnswer; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@Slf4j +class TbWebSocketHandlerTest { + + TbWebSocketHandler wsHandler; + NativeWebSocketSession session; + Session nativeSession; + RemoteEndpoint.Async asyncRemote; + WebSocketSessionRef sessionRef; + int maxMsgQueuePerSession; + TbWebSocketHandler.SessionMetaData sendHandler; + ExecutorService executor; + + @BeforeEach + void setUp() throws IOException { + maxMsgQueuePerSession = 100; + executor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName(getClass().getSimpleName())); + wsHandler = spy(new TbWebSocketHandler()); + willDoNothing().given(wsHandler).close(any(), any()); + session = mock(NativeWebSocketSession.class); + nativeSession = mock(Session.class); + willReturn(nativeSession).given(session).getNativeSession(Session.class); + asyncRemote = mock(RemoteEndpoint.Async.class); + willReturn(asyncRemote).given(nativeSession).getAsyncRemote(); + sessionRef = mock(WebSocketSessionRef.class, Mockito.RETURNS_DEEP_STUBS); //prevent NPE on logs + sendHandler = spy(wsHandler.new SessionMetaData(session, sessionRef, maxMsgQueuePerSession)); + } + + @AfterEach + void tearDown() { + if (executor != null) { + executor.shutdownNow(); + } + } + + @Test + void sendHandler_sendMsg_parallel_no_race() throws InterruptedException { + CountDownLatch finishLatch = new CountDownLatch(maxMsgQueuePerSession * 2); + AtomicInteger sendersCount = new AtomicInteger(); + willAnswer(invocation -> { + assertThat(sendersCount.incrementAndGet()).as("no race").isEqualTo(1); + String text = invocation.getArgument(0); + SendHandler onResultHandler = invocation.getArgument(1); + SendResult sendResult = new SendResult(); + executor.submit(() -> { + sendersCount.decrementAndGet(); + onResultHandler.onResult(sendResult); + finishLatch.countDown(); + }); + return null; + }).given(asyncRemote).sendText(anyString(), any()); + + assertThat(sendHandler.isSending.get()).as("sendHandler not is in sending state").isFalse(); + //first batch + IntStream.range(0, maxMsgQueuePerSession).parallel().forEach(i -> sendHandler.sendMsg("hello " + i)); + Awaitility.await("first batch processed").atMost(30, TimeUnit.SECONDS).until(() -> finishLatch.getCount() == maxMsgQueuePerSession); + assertThat(sendHandler.isSending.get()).as("sendHandler not is in sending state").isFalse(); + //second batch - to test pause between big msg batches + IntStream.range(100, 100 + maxMsgQueuePerSession).parallel().forEach(i -> sendHandler.sendMsg("hello " + i)); + assertThat(finishLatch.await(30, TimeUnit.SECONDS)).as("all callbacks fired").isTrue(); + + verify(sendHandler, never()).closeSession(any()); + verify(sendHandler, times(maxMsgQueuePerSession * 2)).onResult(any()); + assertThat(sendHandler.isSending.get()).as("sendHandler not is in sending state").isFalse(); + } + + @Test + void sendHandler_sendMsg_message_order() throws InterruptedException { + CountDownLatch finishLatch = new CountDownLatch(maxMsgQueuePerSession); + Collection outputs = new ConcurrentLinkedQueue<>(); + willAnswer(invocation -> { + String text = invocation.getArgument(0); + outputs.add(text); + SendHandler onResultHandler = invocation.getArgument(1); + SendResult sendResult = new SendResult(); + executor.submit(() -> { + onResultHandler.onResult(sendResult); + finishLatch.countDown(); + }); + return null; + }).given(asyncRemote).sendText(anyString(), any()); + + List inputs = IntStream.range(0, maxMsgQueuePerSession).mapToObj(i -> "msg " + i).collect(Collectors.toList()); + inputs.forEach(s -> sendHandler.sendMsg(s)); + + assertThat(finishLatch.await(30, TimeUnit.SECONDS)).as("all callbacks fired").isTrue(); + assertThat(outputs).as("inputs exactly the same as outputs").containsExactlyElementsOf(inputs); + + verify(sendHandler, never()).closeSession(any()); + verify(sendHandler, times(maxMsgQueuePerSession)).onResult(any()); + } + + @Test + void sendHandler_sendMsg_queue_size_exceed() { + willDoNothing().given(asyncRemote).sendText(anyString(), any()); // send text will never call back, so queue will grow each sendMsg + sendHandler.sendMsg("first message to stay in-flight all the time during this test"); + IntStream.range(0, maxMsgQueuePerSession).parallel().forEach(i -> sendHandler.sendMsg("hello " + i)); + verify(sendHandler, never()).closeSession(any()); + sendHandler.sendMsg("excessive message"); + verify(sendHandler, times(1)).closeSession(eq(new CloseStatus(1008, "Max pending updates limit reached!"))); + verify(asyncRemote, times(1)).sendText(anyString(), any()); + } + +} From 0d55ac97604829ed73145a5d3313f397e9a4f56a Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 14 Jul 2023 13:24:02 +0300 Subject: [PATCH 070/168] make X509 certificate doc link be always available --- .../thingsboard/server/dao/device/DeviceServiceImpl.java | 6 ++++++ .../server/dao/util/DeviceConnectivityUtil.java | 8 -------- 2 files changed, 6 insertions(+), 8 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 91d591171f..0e8ea653ef 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 @@ -755,6 +755,9 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Fri, 14 Jul 2023 13:29:51 +0300 Subject: [PATCH 071/168] make X509 certificate doc link be always available --- .../org/thingsboard/server/dao/device/DeviceServiceImpl.java | 4 ++-- 1 file changed, 2 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 0e8ea653ef..376133b173 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 @@ -755,7 +755,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Fri, 14 Jul 2023 13:54:43 +0300 Subject: [PATCH 072/168] UI: Move API usage dashboard to assets. Update deprecated dashboards resolvers by resolver functions. --- .../api-usage/api-usage-routing.module.ts | 18 ++- .../pages/api-usage/api-usage.component.ts | 16 +-- .../home-links/home-links-routing.module.ts | 111 ++++++++---------- .../dashboard/api_usage.json} | 0 4 files changed, 74 insertions(+), 71 deletions(-) rename ui-ngx/src/{app/modules/home/pages/api-usage/api_usage_json.raw => assets/dashboard/api_usage.json} (100%) diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts index a38fbf9958..7d4bb5fe14 100644 --- a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts @@ -14,10 +14,21 @@ /// limitations under the License. /// -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { inject, NgModule } from '@angular/core'; +import { ActivatedRouteSnapshot, ResolveFn, RouterModule, RouterStateSnapshot, Routes } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { ApiUsageComponent } from '@home/pages/api-usage/api-usage.component'; +import { Dashboard } from '@shared/models/dashboard.models'; +import { ResourcesService } from '@core/services/resources.service'; +import { Observable } from 'rxjs'; + +const apiUsageDashboardJson = '/assets/dashboard/api_usage.json'; + +export const apiUsageDashboardResolver: ResolveFn = ( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot, + resourcesService = inject(ResourcesService) +): Observable => resourcesService.loadJsonResource(apiUsageDashboardJson); const routes: Routes = [ { @@ -30,6 +41,9 @@ const routes: Routes = [ label: 'api-usage.api-usage', icon: 'insert_chart' } + }, + resolve: { + apiUsageDashboard: apiUsageDashboardResolver } } ]; diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.ts b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.ts index 0bcae0801b..e84a23fb66 100644 --- a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.ts +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.ts @@ -14,28 +14,24 @@ /// limitations under the License. /// -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; -import apiUsageDashboardJson from '!raw-loader!./api_usage_json.raw'; import { Dashboard } from '@shared/models/dashboard.models'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'tb-api-usage', templateUrl: './api-usage.component.html', styleUrls: ['./api-usage.component.scss'] }) -export class ApiUsageComponent extends PageComponent implements OnInit { +export class ApiUsageComponent extends PageComponent { - apiUsageDashboard: Dashboard; + apiUsageDashboard: Dashboard = this.route.snapshot.data.apiUsageDashboard; - constructor(protected store: Store) { + constructor(protected store: Store, + private route: ActivatedRoute) { super(store); } - - ngOnInit() { - this.apiUsageDashboard = JSON.parse(apiUsageDashboardJson); - } - } diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts b/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts index c1c1c9bc9e..a11ae14231 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts @@ -14,8 +14,8 @@ /// limitations under the License. /// -import { Injectable, NgModule } from '@angular/core'; -import { Resolve, RouterModule, Routes } from '@angular/router'; +import { inject, NgModule } from '@angular/core'; +import { ActivatedRouteSnapshot, ResolveFn, RouterModule, RouterStateSnapshot, Routes } from '@angular/router'; import { HomeLinksComponent } from './home-links.component'; import { Authority } from '@shared/models/authority.enum'; @@ -33,62 +33,58 @@ const sysAdminHomePageJson = '/assets/dashboard/sys_admin_home_page.json'; const tenantAdminHomePageJson = '/assets/dashboard/tenant_admin_home_page.json'; const customerUserHomePageJson = '/assets/dashboard/customer_user_home_page.json'; -@Injectable() -export class HomeDashboardResolver implements Resolve { - - constructor(private dashboardService: DashboardService, - private resourcesService: ResourcesService, - private store: Store) { - } - - resolve(): Observable { - return this.dashboardService.getHomeDashboard().pipe( - mergeMap((dashboard) => { - if (!dashboard) { - let dashboard$: Observable; - const authority = getCurrentAuthUser(this.store).authority; - switch (authority) { - case Authority.SYS_ADMIN: - dashboard$ = this.resourcesService.loadJsonResource(sysAdminHomePageJson); - break; - case Authority.TENANT_ADMIN: - dashboard$ = this.updateDeviceActivityKeyFilterIfNeeded(this.resourcesService.loadJsonResource(tenantAdminHomePageJson)); - break; - case Authority.CUSTOMER_USER: - dashboard$ = this.updateDeviceActivityKeyFilterIfNeeded(this.resourcesService.loadJsonResource(customerUserHomePageJson)); - break; - } - if (dashboard$) { - return dashboard$.pipe( - map((homeDashboard) => { - homeDashboard.hideDashboardToolbar = true; - return homeDashboard; - }) - ); +const updateDeviceActivityKeyFilterIfNeeded = (store: Store, + dashboard$: Observable): Observable => + store.pipe(select(selectPersistDeviceStateToTelemetry)).pipe( + mergeMap((persistToTelemetry) => dashboard$.pipe( + map((dashboard) => { + if (persistToTelemetry) { + for (const filterId of Object.keys(dashboard.configuration.filters)) { + if (['Active Devices', 'Inactive Devices'].includes(dashboard.configuration.filters[filterId].filter)) { + dashboard.configuration.filters[filterId].keyFilters[0].key.type = EntityKeyType.TIME_SERIES; + } } } - return of(dashboard); + return dashboard; }) - ); - } + )) + ); - private updateDeviceActivityKeyFilterIfNeeded(dashboard$: Observable): Observable { - return this.store.pipe(select(selectPersistDeviceStateToTelemetry)).pipe( - mergeMap((persistToTelemetry) => dashboard$.pipe( - map((dashboard) => { - if (persistToTelemetry) { - for (const filterId of Object.keys(dashboard.configuration.filters)) { - if (['Active Devices', 'Inactive Devices'].includes(dashboard.configuration.filters[filterId].filter)) { - dashboard.configuration.filters[filterId].keyFilters[0].key.type = EntityKeyType.TIME_SERIES; - } - } - } - return dashboard; - }) - )) - ); - } -} +export const homeDashboardResolver: ResolveFn = ( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot, + dashboardService = inject(DashboardService), + resourcesService = inject(ResourcesService), + store: Store = inject(Store) +): Observable => + dashboardService.getHomeDashboard().pipe( + mergeMap((dashboard) => { + if (!dashboard) { + let dashboard$: Observable; + const authority = getCurrentAuthUser(store).authority; + switch (authority) { + case Authority.SYS_ADMIN: + dashboard$ = resourcesService.loadJsonResource(sysAdminHomePageJson); + break; + case Authority.TENANT_ADMIN: + dashboard$ = updateDeviceActivityKeyFilterIfNeeded(store, resourcesService.loadJsonResource(tenantAdminHomePageJson)); + break; + case Authority.CUSTOMER_USER: + dashboard$ = updateDeviceActivityKeyFilterIfNeeded(store, resourcesService.loadJsonResource(customerUserHomePageJson)); + break; + } + if (dashboard$) { + return dashboard$.pipe( + map((homeDashboard) => { + homeDashboard.hideDashboardToolbar = true; + return homeDashboard; + }) + ); + } + } + return of(dashboard); + }) + ); const routes: Routes = [ { @@ -103,16 +99,13 @@ const routes: Routes = [ } }, resolve: { - homeDashboard: HomeDashboardResolver + homeDashboard: homeDashboardResolver } } ]; @NgModule({ imports: [RouterModule.forChild(routes)], - exports: [RouterModule], - providers: [ - HomeDashboardResolver - ] + exports: [RouterModule] }) export class HomeLinksRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw b/ui-ngx/src/assets/dashboard/api_usage.json similarity index 100% rename from ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw rename to ui-ngx/src/assets/dashboard/api_usage.json From 038ab25403c655659c74f69c9fcb9e9e447ae925 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 14 Jul 2023 15:48:10 +0300 Subject: [PATCH 073/168] UI: Add device connectivity dialog; improve work ngrx store from user settings --- ui-ngx/src/app/core/auth/auth.actions.ts | 19 +- ui-ngx/src/app/core/auth/auth.effects.ts | 14 ++ ui-ngx/src/app/core/auth/auth.reducer.ts | 15 +- ui-ngx/src/app/core/auth/auth.selectors.ts | 6 + ui-ngx/src/app/core/http/device.service.ts | 4 + ui-ngx/src/app/core/utils.ts | 2 + .../wizard/device-wizard-dialog.component.ts | 9 +- ...e-check-connectivity-dialog.component.html | 226 ++++++++++++++++++ ...e-check-connectivity-dialog.component.scss | 151 ++++++++++++ ...ice-check-connectivity-dialog.component.ts | 170 +++++++++++++ .../home/pages/device/device.component.html | 6 + .../home/pages/device/device.module.ts | 4 +- .../device/devices-table-config.resolver.ts | 35 ++- .../shared/components/markdown.component.scss | 2 +- ui-ngx/src/app/shared/models/device.models.ts | 16 +- .../app/shared/models/user-settings.models.ts | 1 + .../help/en_US/device/install_coap_client.md | 40 ++++ .../assets/help/en_US/device/install_curl.md | 34 +++ .../help/en_US/device/install_mqtt_client.md | 38 +++ .../assets/locale/locale.constant-en_US.json | 27 ++- ui-ngx/src/form.scss | 49 ++-- ui-ngx/src/typings/utils.d.ts | 21 ++ 22 files changed, 846 insertions(+), 43 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts create mode 100644 ui-ngx/src/assets/help/en_US/device/install_coap_client.md create mode 100644 ui-ngx/src/assets/help/en_US/device/install_curl.md create mode 100644 ui-ngx/src/assets/help/en_US/device/install_mqtt_client.md create mode 100644 ui-ngx/src/typings/utils.d.ts diff --git a/ui-ngx/src/app/core/auth/auth.actions.ts b/ui-ngx/src/app/core/auth/auth.actions.ts index 67b122d67f..2e8c82ae2d 100644 --- a/ui-ngx/src/app/core/auth/auth.actions.ts +++ b/ui-ngx/src/app/core/auth/auth.actions.ts @@ -17,6 +17,7 @@ import { Action } from '@ngrx/store'; import { User } from '@shared/models/user.model'; import { AuthPayload } from '@core/auth/auth.models'; +import { UserSettings } from '@shared/models/user-settings.models'; export enum AuthActionTypes { AUTHENTICATED = '[Auth] Authenticated', @@ -25,7 +26,9 @@ export enum AuthActionTypes { UPDATE_USER_DETAILS = '[Auth] Update User Details', UPDATE_LAST_PUBLIC_DASHBOARD_ID = '[Auth] Update Last Public Dashboard Id', UPDATE_HAS_REPOSITORY = '[Auth] Change Has Repository', - UPDATE_OPENED_MENU_SECTION = '[Preferences] Update Opened Menu Section' + UPDATE_OPENED_MENU_SECTION = '[Preferences] Update Opened Menu Section', + UPDATE_USER_SETTINGS = '[Preferences] Update user settings', + DELETE_USER_SETTINGS = '[Preferences] Delete user settings', } export class ActionAuthAuthenticated implements Action { @@ -68,6 +71,18 @@ export class ActionPreferencesUpdateOpenedMenuSection implements Action { constructor(readonly payload: { path: string; opened: boolean }) {} } +export class ActionPreferencesUpdateUserSettings implements Action { + readonly type = AuthActionTypes.UPDATE_USER_SETTINGS; + + constructor(readonly payload: Partial) {} +} + +export class ActionPreferencesDeleteUserSettings implements Action { + readonly type = AuthActionTypes.DELETE_USER_SETTINGS; + + constructor(readonly payload: Array>) {} +} + export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated | ActionAuthLoadUser | ActionAuthUpdateUserDetails | ActionAuthUpdateLastPublicDashboardId | ActionAuthUpdateHasRepository | - ActionPreferencesUpdateOpenedMenuSection; + ActionPreferencesUpdateOpenedMenuSection | ActionPreferencesUpdateUserSettings | ActionPreferencesDeleteUserSettings; diff --git a/ui-ngx/src/app/core/auth/auth.effects.ts b/ui-ngx/src/app/core/auth/auth.effects.ts index d2ebe2b4c5..76b9dce9fa 100644 --- a/ui-ngx/src/app/core/auth/auth.effects.ts +++ b/ui-ngx/src/app/core/auth/auth.effects.ts @@ -39,4 +39,18 @@ export class AuthEffects { withLatestFrom(this.store.pipe(select(selectAuthState))), mergeMap(([action, state]) => this.userSettingsService.putUserSettings({ openedMenuSections: state.userSettings.openedMenuSections })) ), {dispatch: false}); + + updatedUserSettings = createEffect(() => this.actions$.pipe( + ofType( + AuthActionTypes.UPDATE_USER_SETTINGS, + ), + mergeMap((state) => this.userSettingsService.putUserSettings(state.payload)) + ), {dispatch: false}); + + deleteUserSettings = createEffect(() => this.actions$.pipe( + ofType( + AuthActionTypes.DELETE_USER_SETTINGS, + ), + mergeMap((state) => this.userSettingsService.deleteUserSettings(state.payload)) + ), {dispatch: false}); } diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts index 0847654399..6fd80d7052 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -16,7 +16,8 @@ import { AuthPayload, AuthState } from './auth.models'; import { AuthActions, AuthActionTypes } from './auth.actions'; -import { initialUserSettings } from '@shared/models/user-settings.models'; +import { initialUserSettings, UserSettings } from '@shared/models/user-settings.models'; +import { unset } from '@core/utils'; const emptyUserAuthState: AuthPayload = { authUser: null, @@ -42,6 +43,7 @@ export const authReducer = ( state: AuthState = initialState, action: AuthActions ): AuthState => { + let userSettings: UserSettings; switch (action.type) { case AuthActionTypes.AUTHENTICATED: return { ...state, isAuthenticated: true, ...action.payload }; @@ -71,7 +73,16 @@ export const authReducer = ( } else { openedMenuSections.delete(action.payload.path); } - const userSettings = {...state.userSettings, ...{ openedMenuSections: Array.from(openedMenuSections)}}; + userSettings = {...state.userSettings, ...{ openedMenuSections: Array.from(openedMenuSections)}}; + return { ...state, ...{ userSettings }}; + + case AuthActionTypes.UPDATE_USER_SETTINGS: + userSettings = {...state.userSettings, ...action.payload}; + return { ...state, ...{ userSettings }}; + + case AuthActionTypes.DELETE_USER_SETTINGS: + userSettings = {...state.userSettings}; + action.payload.forEach(path => unset(userSettings, path)); return { ...state, ...{ userSettings }}; default: diff --git a/ui-ngx/src/app/core/auth/auth.selectors.ts b/ui-ngx/src/app/core/auth/auth.selectors.ts index 2e8406293e..4bf2f2276d 100644 --- a/ui-ngx/src/app/core/auth/auth.selectors.ts +++ b/ui-ngx/src/app/core/auth/auth.selectors.ts @@ -21,6 +21,7 @@ import { AuthState } from './auth.models'; import { take } from 'rxjs/operators'; import { AuthUser } from '@shared/models/user.model'; import { UserSettings } from '@shared/models/user-settings.models'; +import { getDescendantProp } from '@core/utils'; export const selectAuthState = createFeatureSelector< AuthState>( 'auth' @@ -76,6 +77,11 @@ export const selectUserSettings = createSelector( (state: AuthState) => state.userSettings ); +export const selectUserSettingsProperty = (path: NestedKeyOf) => createSelector( + selectAuthState, + (state: AuthState) => getDescendantProp(state.userSettings, path) +); + export const selectOpenedMenuSections = createSelector( selectAuthState, (state: AuthState) => state.userSettings.openedMenuSections diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index dfc2d674a2..44e91e43f8 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -208,4 +208,8 @@ export class DeviceService { return this.http.post('/api/device/bulk_import', entitiesData, defaultHttpOptionsFromConfig(config)); } + public getDevicePublishTelemetryCommands(deviceId: string, config?: RequestConfig): Observable<{[key: string]: string}> { + return this.http.get<{[key: string]: string}>(`/api/device/${deviceId}/commands`, defaultHttpOptionsFromConfig(config)); + } + } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index c310693b03..d6a3c3c6e3 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -315,6 +315,8 @@ export const isEqual = (a: any, b: any): boolean => _.isEqual(a, b); export const isEmpty = (a: any): boolean => _.isEmpty(a); +export const unset = (object: any, path: string | symbol): boolean => _.unset(object, path); + export const isEqualIgnoreUndefined = (a: any, b: any): boolean => { if (a === b) { return true; diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index 65e7df1e90..8210f9a533 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -23,7 +23,6 @@ import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; import { Device, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models'; import { MatStepper, StepperOrientation } from '@angular/material/stepper'; -import { BaseData, HasId } from '@shared/models/base-data'; import { EntityType } from '@shared/models/entity-type.models'; import { Observable, throwError } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; @@ -40,7 +39,7 @@ import { HttpErrorResponse } from '@angular/common/http'; templateUrl: './device-wizard-dialog.component.html', styleUrls: ['./device-wizard-dialog.component.scss'] }) -export class DeviceWizardDialogComponent extends DialogComponent { +export class DeviceWizardDialogComponent extends DialogComponent { @ViewChild('addDeviceWizardStepper', {static: true}) addDeviceWizardStepper: MatStepper; @@ -64,7 +63,7 @@ export class DeviceWizardDialogComponent extends DialogComponent, protected router: Router, - public dialogRef: MatDialogRef, + public dialogRef: MatDialogRef, private deviceService: DeviceService, private breakpointObserver: BreakpointObserver, private fb: FormBuilder) { @@ -121,7 +120,7 @@ export class DeviceWizardDialogComponent extends DialogComponent this.dialogRef.close(true) + (device) => this.dialogRef.close(device) ); } } @@ -137,7 +136,7 @@ export class DeviceWizardDialogComponent extends DialogComponent> { + private createDevice(): Observable { const device: Device = { name: this.deviceWizardFormGroup.get('name').value, label: this.deviceWizardFormGroup.get('label').value, diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html new file mode 100644 index 0000000000..75e8887da6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html @@ -0,0 +1,226 @@ + + +

device.connectivity.check-connectivity

+ + + +
+
+
+ + + {{ deviceTransportTypeTranslationMap.get(BasicTransportType.HTTP) | translate }} + + + {{ deviceTransportTypeTranslationMap.get(DeviceTransportType.MQTT) | translate }} + + + {{ deviceTransportTypeTranslationMap.get(DeviceTransportType.COAP) | translate }} + + + {{ deviceTransportTypeTranslationMap.get(DeviceTransportType.SNMP) | translate }} + + + {{ deviceTransportTypeTranslationMap.get(DeviceTransportType.LWM2M) | translate }} + + +
+ + +
device.connectivity.use-following-instructions
+
+ device.connectivity.install-curl + +
+
+
device.connectivity.http-command
+ +
+
+
device.connectivity.https-command
+ +
+
+ +
+
device.connectivity.use-following-instructions
+
+ device.connectivity.install-mqtt-client + +
+
+
+
device.connectivity.mqtt-command
+ +
+
+
+
device.connectivity.mqtts-command
+ +
+ +
device.connectivity.mqtts-x509-command
+ +
+
+
+ +
+
device.connectivity.use-following-instructions
+
+ device.connectivity.install-coap-cli + +
+
+
+
device.connectivity.coap-command
+ +
+
+
+
device.connectivity.coaps-command
+ +
+ +
device.connectivity.coaps-x509-command
+ +
+
+
+ +
device.connectivity.snmp-command
+ +
+ +
device.connectivity.lwm2m-command
+ +
+
+
+
+
+
device.state
+
+ {{ (status ? 'device.active' : 'device.inactive') | translate }} +
+
+
attribute.latest-telemetry
+
+
+
device.time
+
attribute.key
+
attribute.value
+
+
+
+
{{ telemetry.lastUpdateTs | date: 'yyyy-MM-dd HH:mm:ss' }}
+
{{ telemetry.key }}
+
{{ telemetry.value }}
+
+
+
+
+
+
+
+ {{ 'action.dont-show-again' | translate}} + + +
+ +
+ + + {{ 'device.connectivity.loading-check-connectivity-command' | translate }} + +
+
+ +
+
+
attribute.no-latest-telemetry
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss new file mode 100644 index 0000000000..e7c88bb2cb --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss @@ -0,0 +1,151 @@ +/** + * 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. + */ + +@import "../../../../../scss/constants"; + +:host { + height: 100%; + max-height: 100vh; + display: grid; + grid-template-rows: min-content minmax(auto, 1fr) min-content; + + .tb-loader { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + height: 300px; + max-height: 100%; + + .label { + margin-bottom: 0; + text-align: center; + } + } + + .status { + margin-left: 12px; + border-radius: 12px; + height: 24px; + line-height: 24px; + padding: 0 8px; + width: fit-content; + color: #198038; + background-color: rgba(25, 128, 56, 0.08); + font-size: 14px; + + &.inactive { + color: #d12730; + background-color: rgba(209, 39, 48, 0.08); + } + } + + .tb-hint-instruction { + border-radius: 6px; + background-color: rgba(48, 86, 128, 0.04); + padding: 6px 16px; + + .content { + vertical-align: middle; + } + } + + .tb-font-14 { + font-size: 14px; + } + + .tb-form-table-body { + max-height: 88px; + overflow-y: auto; + scrollbar-gutter: stable; + + .tb-form-table-row { + min-height: 38px; + } + } + + .tb-no-data-available { + .tb-no-data-bg { + min-height: 68px; + } + } + + @media #{$mat-sm} { + width: 470px; + } + + @media #{$mat-gt-sm} { + width: 720px; + } +} + +:host-context(.mat-mdc-dialog-container) { + .tb-dialog-actions { + display: flex; + gap: 8px; + padding: 8px 16px; + } + + .mat-mdc-dialog-content { + max-height: 80vh; + padding: 16px; + } +} + +:host ::ng-deep { + .tb-markdown-view { + .tb-command-code { + .code-wrapper { + padding: 0; + pre[class*=language-] { + background: #F3F6FA; + border-color: #305680; + } + } + button.clipboard-btn { + right: 0; + p { + color: #305680; + } + p, div { + background-color: #F3F6FA; + } + div { + img { + display: none; + } + &:after { + content: ""; + position: initial; + display: block; + width: 18px; + height: 18px; + background: #305680; + mask-image: url(/assets/copy-code-icon.svg); + mask-repeat: no-repeat; + } + } + } + } + } + .mdc-button__label > span { + .mat-icon { + vertical-align: text-bottom; + box-sizing: initial; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts new file mode 100644 index 0000000000..9e1639740e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts @@ -0,0 +1,170 @@ +/// +/// 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. +/// + +import { Component, Inject, NgZone, OnDestroy, OnInit } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { DeviceService } from '@core/http/device.service'; +import { FormBuilder } from '@angular/forms'; +import { + AttributeData, + AttributeScope, + AttributesSubscriptionCmd, + LatestTelemetry, + TelemetrySubscriber +} from '@shared/models/telemetry/telemetry.models'; +import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; +import { selectPersistDeviceStateToTelemetry } from '@core/auth/auth.selectors'; +import { take } from 'rxjs/operators'; +import { + BasicTransportType, + DeviceTransportType, + deviceTransportTypeTranslationMap, + NetworkTransportType +} from '@shared/models/device.models'; +import { UserSettingsService } from '@core/http/user-settings.service'; +import { ActionPreferencesUpdateUserSettings } from '@core/auth/auth.actions'; + +export interface DeviceCheckConnectivityDialogData { + deviceId: EntityId; + showDontShowAgain: boolean; +} +@Component({ + selector: 'tb-device-check-connectivity-dialog', + templateUrl: './device-check-connectivity-dialog.component.html', + styleUrls: ['./device-check-connectivity-dialog.component.scss'] +}) +export class DeviceCheckConnectivityDialogComponent extends + DialogComponent implements OnInit, OnDestroy { + + loadedCommand = false; + + status: boolean; + + latestTelemetry: Array = []; + + commands: {[key: string]: string}; + + allowTransportType = new Set(); + selectTransportType: NetworkTransportType; + + BasicTransportType = BasicTransportType; + DeviceTransportType = DeviceTransportType; + deviceTransportTypeTranslationMap = deviceTransportTypeTranslationMap; + + showDontShowAgain = this.data.showDontShowAgain; + + notShowAgain = false; + + private telemetrySubscriber: TelemetrySubscriber; + + private currentTime = Date.now(); + + private transportTypes = [...Object.keys(BasicTransportType), ...Object.keys(DeviceTransportType)] as Array; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) private data: DeviceCheckConnectivityDialogData, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private deviceService: DeviceService, + private telemetryWsService: TelemetryWebsocketService, + private userSettingsService: UserSettingsService, + private zone: NgZone) { + super(store, router, dialogRef); + } + + ngOnInit() { + this.loadCommands(); + this.subscribeToLatestTelemetry(); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.telemetrySubscriber?.complete(); + this.telemetrySubscriber?.unsubscribe(); + } + + close(): void { + if (this.notShowAgain && this.showDontShowAgain) { + this.store.dispatch(new ActionPreferencesUpdateUserSettings({ notDisplayConnectivityAfterAddDevice: true })); + this.dialogRef.close(null); + } else { + this.dialogRef.close(null); + } + } + + createMarkDownCommand(command: string): string { + return '```bash\n' + + command + + '{:copy-code}\n' + + '```'; + } + + private loadCommands() { + this.deviceService.getDevicePublishTelemetryCommands(this.data.deviceId.id).subscribe( + commands => { + this.commands = commands; + const commandsProtocols = Object.keys(commands); + this.transportTypes.forEach(transport => { + const findCommand = commandsProtocols.find(item => item.toUpperCase().startsWith(transport)); + if (findCommand) { + this.allowTransportType.add(transport); + } + }); + this.selectTransportType = this.allowTransportType.values().next().value; + this.loadedCommand = true; + } + ); + } + + private subscribeToLatestTelemetry() { + this.store.pipe(select(selectPersistDeviceStateToTelemetry)).pipe( + take(1) + ).subscribe(persistToTelemetry => { + this.telemetrySubscriber = TelemetrySubscriber.createEntityAttributesSubscription( + this.telemetryWsService, this.data.deviceId, LatestTelemetry.LATEST_TELEMETRY, this.zone); + if (!persistToTelemetry) { + const subscriptionCommand = new AttributesSubscriptionCmd(); + subscriptionCommand.entityType = this.data.deviceId.entityType as EntityType; + subscriptionCommand.entityId = this.data.deviceId.id; + subscriptionCommand.scope = AttributeScope.SERVER_SCOPE; + subscriptionCommand.keys = 'active'; + this.telemetrySubscriber.subscriptionCommands.push(subscriptionCommand); + } + + this.telemetrySubscriber.subscribe(); + this.telemetrySubscriber.attributeData$().subscribe( + (data) => { + this.latestTelemetry = data.reduce>((accumulator, item) => { + if (item.key === 'active') { + this.status = item.value; + } else if (item.lastUpdateTs > this.currentTime) { + accumulator.push(item); + } + return accumulator; + }, []); + } + ); + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.html b/ui-ngx/src/app/modules/home/pages/device/device.component.html index 244b1c000b..0b1ef5225c 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.html @@ -46,6 +46,12 @@ [fxShow]="!isEdit"> {{ ((deviceScope === 'customer_user' || deviceScope === 'edge_customer_user') ? 'device.view-credentials' : 'device.manage-credentials') | translate }} + - +
Date: Fri, 14 Jul 2023 18:59:20 +0300 Subject: [PATCH 075/168] Refactoring --- .../components/details-panel.component.ts | 5 ++- .../components/event/event-table-config.ts | 2 +- .../rulechain/rule-node-config.component.ts | 16 ++++++++++ .../rule-node-details.component.html | 3 +- .../rulechain/rule-node-details.component.ts | 3 ++ .../rulechain/rulechain-page.component.html | 3 +- .../rulechain/rulechain-page.component.ts | 31 +++++++++---------- .../src/app/shared/models/rule-node.models.ts | 20 +++++------- 8 files changed, 48 insertions(+), 35 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/details-panel.component.ts b/ui-ngx/src/app/modules/home/components/details-panel.component.ts index 66facf08d4..b2f8a059f3 100644 --- a/ui-ngx/src/app/modules/home/components/details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/details-panel.component.ts @@ -15,6 +15,7 @@ /// import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, @@ -55,9 +56,7 @@ export class DetailsPanelComponent extends PageComponent implements OnDestroy { } this.theFormValue = value; if (this.theFormValue !== null) { - this.formSubscription = this.theFormValue.valueChanges.subscribe(() => { - this.cd.detectChanges() - }); + this.formSubscription = this.theFormValue.valueChanges.subscribe(() => this.cd.detectChanges()); } } } diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index 565dbf14bf..4021d018ce 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -359,7 +359,7 @@ export class EventTableConfig extends EntityTableConfig { case DebugEventType.DEBUG_RULE_NODE: if (this.testButtonLabel) { this.cellActionDescriptors.push({ - name: this.translate.instant('rulenode.test-with-this-message', {test: this.testButtonLabel}), + name: this.translate.instant('rulenode.test-with-this-message', {test: this.translate.instant(this.testButtonLabel)}), icon: 'bug_report', isEnabled: (entity) => entity.body.type === 'IN', onAction: ($event, entity) => { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts index 6c19507301..3f3071221e 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts @@ -87,6 +87,9 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On @Output() initRuleNode = new EventEmitter(); + @Output() + changeScript = new EventEmitter(); + nodeDefinitionValue: RuleNodeDefinition; @Input() @@ -110,6 +113,8 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On changeSubscription: Subscription; + changeScriptSubscription: Subscription; + definedConfigComponent: IRuleNodeConfigurationComponent; private definedConfigComponentRef: ComponentRef; @@ -140,6 +145,14 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On if (this.definedConfigComponentRef) { this.definedConfigComponentRef.destroy(); } + if (this.changeSubscription) { + this.changeSubscription.unsubscribe(); + this.changeSubscription = null; + } + if (this.changeScriptSubscription) { + this.changeScriptSubscription.unsubscribe(); + this.changeScriptSubscription = null; + } } ngAfterViewInit(): void { @@ -212,6 +225,9 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On this.changeSubscription = this.definedConfigComponent.configurationChanged.subscribe((configuration) => { this.updateModel(configuration); }); + if (this.definedConfigComponent?.changeScript) { + this.changeScriptSubscription = this.definedConfigComponent.changeScript.subscribe(() => this.changeScript.emit()); + } } } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html index 2f925ded60..34aa8167e3 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html @@ -51,7 +51,8 @@ [ruleChainId]="ruleChainId" [ruleChainType]="ruleChainType" [nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition" - (initRuleNode)="initRuleNode.emit($event)"> + (initRuleNode)="initRuleNode.emit($event)" + (changeScript)="changeScript.emit($event)">
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts index 7b0f426c35..f1d6001c88 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts @@ -58,6 +58,9 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O @Output() initRuleNode = new EventEmitter(); + @Output() + changeScript = new EventEmitter(); + ruleNodeType = RuleNodeType; entityType = EntityType; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html index ba91b45f16..5f5594cc1a 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html @@ -112,7 +112,8 @@ [ruleChainType]="ruleChainType" [isEdit]="true" [isReadOnly]="false" - (initRuleNode)="onRuleNodeInit()"> + (initRuleNode)="onRuleNodeInit()" + (changeScript)="switchToFirstTab()"> diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts index d5ea093721..6b78d94ac7 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts @@ -1279,25 +1279,24 @@ export class RuleChainPageComponent extends PageComponent } onDebugEventSelected(debugEventBody: DebugRuleNodeEventBody) { - if (this.ruleNodeComponent.ruleNodeConfigComponent.useDefinedDirective() && - this.ruleNodeComponent.ruleNodeConfigComponent.definedConfigComponent.getSupportTestFunction() && - this.ruleNodeComponent.ruleNodeConfigComponent.definedConfigComponent.testScript$) { - this.ruleNodeComponent.ruleNodeConfigComponent.definedConfigComponent.testScript$(debugEventBody) - .subscribe((value) => { - if (value) { - this.selectedRuleNodeTabIndex = 0; - } - }) - } + const ruleNodeConfigComponent = this.ruleNodeComponent.ruleNodeConfigComponent; + const ruleNodeConfigDefinedComponent = ruleNodeConfigComponent.definedConfigComponent; + if (ruleNodeConfigComponent.useDefinedDirective() && ruleNodeConfigDefinedComponent.hasScript && ruleNodeConfigDefinedComponent.testScript) { + ruleNodeConfigDefinedComponent.testScript(debugEventBody); + } } onRuleNodeInit() { - if (this.ruleNodeComponent.ruleNodeConfigComponent.useDefinedDirective() && - this.ruleNodeComponent.ruleNodeConfigComponent.definedConfigComponent.getSupportTestFunction()) { - this.ruleNodeTestButtonLabel = this.ruleNodeComponent.ruleNodeConfigComponent.definedConfigComponent.getTestButtonLabel(); - } else { - this.ruleNodeTestButtonLabel = ''; - } + const ruleNodeConfigDefinedComponent = this.ruleNodeComponent.ruleNodeConfigComponent.definedConfigComponent; + if (this.ruleNodeComponent.ruleNodeConfigComponent.useDefinedDirective() && ruleNodeConfigDefinedComponent.hasScript) { + this.ruleNodeTestButtonLabel = ruleNodeConfigDefinedComponent.testScriptLabel; + } else { + this.ruleNodeTestButtonLabel = ''; + } + } + + switchToFirstTab() { + this.selectedRuleNodeTabIndex = 0; } saveRuleNode() { diff --git a/ui-ngx/src/app/shared/models/rule-node.models.ts b/ui-ngx/src/app/shared/models/rule-node.models.ts index dd427aaf09..2a7dedabec 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -73,13 +73,14 @@ export interface RuleNodeConfigurationDescriptor { export interface IRuleNodeConfigurationComponent { ruleNodeId: string; ruleChainId: string; + hasScript: boolean; + testScriptLabel?: string; + changeScript?: EventEmitter; ruleChainType: RuleChainType; configuration: RuleNodeConfiguration; configurationChanged: Observable; validate(); - getSupportTestFunction(): boolean; - getTestButtonLabel? (): string; - testScript$? (debugEventBody?: DebugRuleNodeEventBody): Observable; + testScript? (debugEventBody?: DebugRuleNodeEventBody); [key: string]: any; } @@ -92,6 +93,8 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple ruleChainId: string; + hasScript: boolean = false; + ruleChainType: RuleChainType; configurationValue: RuleNodeConfiguration; @@ -115,8 +118,7 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple configurationChangedEmiter = new EventEmitter(); configurationChanged = this.configurationChangedEmiter.asObservable(); - protected constructor(@Inject(Store) protected store: Store, - @Inject(TranslateService) protected translate: TranslateService) { + protected constructor(@Inject(Store) protected store: Store) { super(store); } @@ -134,14 +136,6 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple this.onValidate(); } - getSupportTestFunction(): boolean { - return false; - } - - getTestButtonLabel(): string { - return this.translate.instant('rulenode.test-script-function'); - } - protected setupConfiguration(configuration: RuleNodeConfiguration) { this.onConfigurationSet(this.prepareInputConfig(configuration)); this.updateValidators(false); From 2d1ef5db8c19f52f196a886e03581d7e37439643 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 14 Jul 2023 19:30:18 +0300 Subject: [PATCH 076/168] UI: Improve load units models --- .../shared/components/unit-input.component.ts | 67 +- ui-ngx/src/app/shared/models/unit.models.ts | 2023 +---------------- ui-ngx/src/assets/model/units.json | 2022 ++++++++++++++++ 3 files changed, 2068 insertions(+), 2044 deletions(-) create mode 100644 ui-ngx/src/assets/model/units.json diff --git a/ui-ngx/src/app/shared/components/unit-input.component.ts b/ui-ngx/src/app/shared/components/unit-input.component.ts index 4b70c31cec..8700151c69 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.ts +++ b/ui-ngx/src/app/shared/components/unit-input.component.ts @@ -15,11 +15,18 @@ /// import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; -import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, UntypedFormBuilder } from '@angular/forms'; -import { Observable, of } from 'rxjs'; -import { searchUnits, Unit, unitBySymbol, units } from '@shared/models/unit.models'; -import { map, mergeMap, startWith, tap } from 'rxjs/operators'; +import { ControlValueAccessor, FormBuilder, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { EMPTY, Observable, of, ReplaySubject, switchMap } from 'rxjs'; +import { searchUnits, Unit, unitBySymbol } from '@shared/models/unit.models'; +import { map, mergeMap, share, startWith, tap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; +import { ResourcesService } from '@core/services/resources.service'; + +const unitsModels = '/assets/model/units.json'; + +interface UnitsJson { + units: Array; +} @Component({ selector: 'tb-unit-input', @@ -51,13 +58,12 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit { private dirty = false; - private translatedUnits: Array = units.map(u => ({symbol: u.symbol, - name: this.translate.instant(u.name), - tags: u.tags})); + private fetchUnits$: Observable> = null; private propagateChange = (_val: any) => {}; - constructor(private fb: UntypedFormBuilder, + constructor(private fb: FormBuilder, + private resourcesService: ResourcesService, private translate: TranslateService) { } @@ -68,7 +74,6 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit { tap(value => { this.updateView(value); }), - startWith(''), map(value => (value as Unit)?.symbol ? (value as Unit).symbol : (value ? value as string : '')), mergeMap(symbol => this.fetchUnits(symbol) ) ); @@ -77,13 +82,15 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit { writeValue(symbol?: string): void { this.searchText = ''; this.modelValue = symbol; - let res: Unit | string = null; - if (symbol) { - const unit = unitBySymbol(symbol); - res = unit ? unit : symbol; - } - this.unitsFormControl.patchValue(res, {emitEvent: false}); - this.dirty = true; + EMPTY.pipe( + startWith(''), + switchMap(() => symbol + ? this.unitsConstant().pipe(map(units => unitBySymbol(units, symbol) ?? symbol)) + : of(null)) + ).subscribe(result => { + this.unitsFormControl.patchValue(result, {emitEvent: false}); + this.dirty = true; + }); } onFocus() { @@ -114,12 +121,9 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit { fetchUnits(searchText?: string): Observable> { this.searchText = searchText; - const result = searchUnits(this.translatedUnits, searchText); - if (result.length) { - return of(result); - } else { - return of([]); - } + return this.unitsConstant().pipe( + map(unit => searchUnits(unit, searchText)) + ); } registerOnChange(fn: any): void { @@ -145,4 +149,23 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit { this.unitInput.nativeElement.focus(); }, 0); } + + private unitsConstant(): Observable> { + if (this.fetchUnits$ === null) { + this.fetchUnits$ = this.resourcesService.loadJsonResource(unitsModels).pipe( + map(units => units.units.map(u => ({ + symbol: u.symbol, + name: this.translate.instant(u.name), + tags: u.tags + }))), + share({ + connector: () => new ReplaySubject(1), + resetOnError: false, + resetOnComplete: false, + resetOnRefCountZero: false + }) + ); + } + return this.fetchUnits$; + } } diff --git a/ui-ngx/src/app/shared/models/unit.models.ts b/ui-ngx/src/app/shared/models/unit.models.ts index e5ca8aa0b3..797e8a0c4a 100644 --- a/ui-ngx/src/app/shared/models/unit.models.ts +++ b/ui-ngx/src/app/shared/models/unit.models.ts @@ -20,2028 +20,7 @@ export interface Unit { tags: string[]; } -export const units: Array = [ - { - name: 'unit.millimeter', - symbol: 'mm', - tags: ['level','height','distance','length','width','gap','depth','millimeter','millimeters','rainfall','precipitation', - 'displacement','position','movement','transition','mm'] - }, - { - name: 'unit.centimeter', - symbol: 'cm', - tags: ['level','height','distance','length','width','gap','depth','centimeter','centimeters','rainfall','precipitation', - 'displacement','position','movement','transition','cm'] - }, - { - name: 'unit.angstrom', - symbol: 'Å', - tags: ['level','height','distance','length','width','gap','depth','atomic scale','atomic distance','nanoscale', - 'angstrom','angstroms','Å'] - }, - { - name: 'unit.nanometer', - symbol: 'nm', - tags: ['level','height','distance','length','width','gap','depth','nanoscale','atomic scale','molecular scale', - 'nanometer','nanometers','nm'] - }, - { - name: 'unit.micrometer', - symbol: 'µm', - tags: ['level','height','distance','length','width','gap','depth','microns','micrometer','micrometers','µm'] - }, - { - name: 'unit.meter', - symbol: 'm', - tags: ['level','height','distance','length','width','gap','depth','meter','meters','m'] - }, - { - name: 'unit.kilometer', - symbol: 'km', - tags: ['distance','height','length','width','gap','depth','kilometer','kilometers','km'] - }, - { - name: 'unit.inch', - symbol: 'in', - tags: ['level','height','distance','length','width','gap','depth','inch','inches','in'] - }, - { - name: 'unit.foot', - symbol: 'ft', - tags: ['level','height','distance','length','width','gap','depth','foot','feet','ft'] - }, - { - name: 'unit.yard', - symbol: 'yd', - tags: ['level','height','distance','length','width','gap','depth','yard','yards','yd'] - }, - { - name: 'unit.mile', - symbol: 'mi', - tags: ['level','height','distance','length','width','gap','depth','mile','miles','mi'] - }, - { - name: 'unit.nautical-mile', - symbol: 'nm', - tags: ['level','height','distance','length','width','gap','depth','nautical mile','nm'] - }, - { - name: 'unit.astronomical-unit', - symbol: 'AU', - tags: ['distance','celestial bodies','solar system','AU'] - }, - { - name: 'unit.reciprocal-metre', - symbol: 'm⁻¹', - tags: ['wavenumber','wave density','wave frequency','m⁻¹'] - }, - { - name: 'unit.meter-per-meter', - symbol: 'm/m', - tags: ['ratio of length to length','meter per meter','m/m'] - }, - { - name: 'unit.steradian', - symbol: 'sr', - tags: ['solid angle','spatial extent','steradian','sr'] - }, - { - name: 'unit.thou', - symbol: 'thou', - tags: ['length','measurement','thou'] - }, - { - name: 'unit.barleycorn', - symbol: 'barleycorn', - tags: ['length','shoe size','barleycorn'] - }, - { - name: 'unit.hand', - symbol: 'hand', - tags: ['length','horse measurement','hand'] - }, - { - name: 'unit.chain', - symbol: 'ch', - tags: ['length','land surveying','ch'] - }, - { - name: 'unit.furlong', - symbol: 'fur', - tags: ['length','land surveying','fur'] - }, - { - name: 'unit.league', - symbol: 'league', - tags: ['length','historical measurement','league'] - }, - { - name: 'unit.fathom', - symbol: 'fathom', - tags: ['depth','nautical measurement','fathom'] - }, - { - name: 'unit.cable', - symbol: 'cable', - tags: ['distance','nautical measurement','cable'] - }, - { - name: 'unit.link', - symbol: 'link', - tags: ['length','land surveying','link'] - }, - { - name: 'unit.rod', - symbol: 'rod', - tags: ['length','land surveying','rod'] - }, - { - name: 'unit.nanogram', - symbol: 'ng', - tags: ['mass','weight','heaviness','load','nanogram','nanograms','ng'] - }, - { - name: 'unit.microgram', - symbol: 'μg', - tags: ['mass','weight','heaviness','load','μg','microgram'] - }, - { - name: 'unit.milligram', - symbol: 'mg', - tags: ['mass','weight','heaviness','load','milligram','miligrams','mg'] - }, - { - name: 'unit.gram', - symbol: 'g', - tags: ['mass','weight','heaviness','load','gram','grams','g'] - }, - { - name: 'unit.kilogram', - symbol: 'kg', - tags: ['mass','weight','heaviness','load','kilogram','kilograms','kg'] - }, - { - name: 'unit.tonne', - symbol: 't', - tags: ['mass','weight','heaviness','load','tonne','tons','t'] - }, - { - name: 'unit.ounce', - symbol: 'oz', - tags: ['mass','weight','heaviness','load','ounce','ounces','oz'] - }, - { - name: 'unit.pound', - symbol: 'lb', - tags: ['mass','weight','heaviness','load','pound','pounds','lb'] - }, - { - name: 'unit.stone', - symbol: 'st', - tags: ['mass','weight','heaviness','load','stone','stones','st'] - }, - { - name: 'unit.hundredweight-count', - symbol: 'cwt', - tags: ['mass','weight','heaviness','load','hundredweight count','cwt'] - }, - { - name: 'unit.short-tons', - symbol: 'short tons', - tags: ['mass','weight','heaviness','load','short ton','short tons'] - }, - { - name: 'unit.dalton', - symbol: 'Da', - tags: ['atomic mass unit','AMU','unified atomic mass unit','dalton','Da'] - }, - { - name: 'unit.grain', - symbol: 'gr', - tags: ['mass','measurement','grain','gr'] - }, - { - name: 'unit.drachm', - symbol: 'dr', - tags: ['mass','measurement','drachm','dr'] - }, - { - name: 'unit.quarter', - symbol: 'qr', - tags: ['mass','measurement','quarter','qr'] - }, - { - name: 'unit.slug', - symbol: 'slug', - tags: ['mass','measurement','slug'] - }, - { - name: 'unit.carat', - symbol: 'ct', - tags: ['gemstone','pearl','jewelry','carat','ct'] - }, - { - name: 'unit.cubic-millimeter', - symbol: 'mm³', - tags: ['volume','capacity','extent','cubic millimeter','mm³'] - }, - { - name: 'unit.cubic-centimeter', - symbol: 'cm³', - tags: ['volume','capacity','extent','cubic centimeter','cubic centimeters','cm³'] - }, - { - name: 'unit.cubic-meter', - symbol: 'm³', - tags: ['volume','capacity','extent','cubic meter','cubic meters','m³'] - }, - { - name: 'unit.cubic-kilometer', - symbol: 'km³', - tags: ['volume','capacity','extent','cubic kilometer','cubic kilometers','km³'] - }, - { - name: 'unit.microliter', - symbol: 'µL', - tags: ['volume','liquid measurement','microliter','µL'] - }, - { - name: 'unit.milliliter', - symbol: 'mL', - tags: ['volume','capacity','extent','milliliter','milliliters','mL'] - }, - { - name: 'unit.liter', - symbol: 'l', - tags: ['volume','capacity','extent','liter','liters','l'] - }, - { - name: 'unit.hectoliter', - symbol: 'hl', - tags: ['volume','capacity','extent','hectoliter','hectoliters','hl'] - }, - { - name: 'unit.cubic-inch', - symbol: 'in³', - tags: ['volume','capacity','extent','cubic inch','cubic inches','in³'] - }, - { - name: 'unit.cubic-foot', - symbol: 'ft³', - tags: ['volume','capacity','extent','cubic foot','cubic feet','ft³'] - }, - { - name: 'unit.cubic-yard', - symbol: 'yd³', - tags: ['volume','capacity','extent','cubic yard','cubic yards','yd³'] - }, - { - name: 'unit.fluid-ounce', - symbol: 'fl-oz', - tags: ['volume','capacity','extent','fluid ounce','fluid ounces','fl-oz'] - }, - { - name: 'unit.pint', - symbol: 'pt', - tags: ['volume','capacity','extent','pint','pints','pt'] - }, - { - name: 'unit.quart', - symbol: 'qt', - tags: ['volume','capacity','extent','quart','quarts','qt'] - }, - { - name: 'unit.gallon', - symbol: 'gal', - tags: ['volume','capacity','extent','gallon','gallons','gal'] - }, - { - name: 'unit.oil-barrels', - symbol: 'bbl', - tags: ['volume','capacity','extent','oil barrel','oil barrels','bbl'] - }, - { - name: 'unit.cubic-meter-per-kilogram', - symbol: 'm³/kg', - tags: ['specific volume','volume per unit mass','cubic meter per kilogram','m³/kg'] - }, - { - name: 'unit.gill', - symbol: 'gi', - tags: ['volume','liquid measurement','gi'] - }, - { - name: 'unit.hogshead', - symbol: 'hhd', - tags: ['volume','liquid measurement','hhd'] - }, - { - name: 'unit.teaspoon', - symbol: 'tsp', - tags: ['volume','cooking measurement','tsp'] - }, - { - name: 'unit.tablespoon', - symbol: 'tbsp', - tags: ['volume','cooking measurement','tbsp'] - }, - { - name: 'unit.cup', - symbol: 'cup', - tags: ['volume','cooking measurement','cup'] - }, - { - name: 'unit.celsius', - symbol: '°C', - tags: ['temperature','heat','cold','warmth','degrees','celsius','shipment condition','°C'] - }, - { - name: 'unit.kelvin', - symbol: 'K', - tags: ['temperature','heat','cold','warmth','degrees','kelvin','K','color quality','white balance','color temperature'] - }, - { - name: 'unit.rankine', - symbol: '°R', - tags: ['temperature','heat','cold','warmth','Rankine','°R'] - }, - { - name: 'unit.fahrenheit', - symbol: '°F', - tags: ['temperature','heat','cold','warmth','degrees','fahrenheit','°F'] - }, - { - name: 'unit.meter-per-second', - symbol: 'm/s', - tags: ['speed','velocity','pace','meter per second','m/s','peak','peak to peak','root mean square (RMS)', - 'vibration','wind speed','weather'] - }, - { - name: 'unit.kilometer-per-hour', - symbol: 'km/h', - tags: ['speed','velocity','pace','kilometer per hour','km/h'] - }, - { - name: 'unit.foot-per-second', - symbol: 'ft/s', - tags: ['speed','velocity','pace','foot per second','ft/s'] - }, - { - name: 'unit.mile-per-hour', - symbol: 'mph', - tags: ['speed','velocity','pace','mile per hour','mph'] - }, - { - name: 'unit.knot', - symbol: 'kt', - tags: ['speed','velocity','pace','knot','knots','kt'] - }, - { - name: 'unit.millimeters-per-minute', - symbol: 'mm/min', - tags: ['feed rate','cutting feed rate','millimeters per minute','mm/min'] - }, - { - name: 'unit.kilometer-per-hour-squared', - symbol: 'km/h²', - tags: ['acceleration','rate of change of velocity','kilometer per hour squared','km/h²'] - }, - { - name: 'unit.foot-per-second-squared', - symbol: 'ft/s²', - tags: ['acceleration','rate of change of velocity','foot per second squared','ft/s²'] - }, - { - name: 'unit.pascal', - symbol: 'Pa', - tags: ['pressure','force','compression','tension','pascal','pascals','Pa','atmospheric pressure','air pressure', - 'weather','altitude','flight'] - }, - { - name: 'unit.kilopascal', - symbol: 'kPa', - tags: ['pressure','force','compression','tension','kilopascal','kilopascals','kPa'] - }, - { - name: 'unit.megapascal', - symbol: 'MPa', - tags: ['pressure','force','compression','tension','megapascal','megapascals','MPa'] - }, - { - name: 'unit.gigapascal', - symbol: 'GPa', - tags: ['pressure','force','compression','tension','gigapascal','gigapascals','GPa'] - }, - { - name: 'unit.millibar', - symbol: 'mbar', - tags: ['pressure','force','compression','tension','millibar','millibars','mbar'] - }, - { - name: 'unit.bar', - symbol: 'bar', - tags: ['pressure','force','compression','tension','bar','bars'] - }, - { - name: 'unit.kilobar', - symbol: 'kbar', - tags: ['pressure','force','compression','tension','kilobar','kilobars','kbar'] - }, - { - name: 'unit.newton', - symbol: 'N', - tags: ['force','pressure','newton','newtons','N','push','pull','weight','gravity','N'] - }, - { - name: 'unit.newton-meter', - symbol: 'Nm', - tags: ['torque','rotational force','newton meter','Nm'] - }, - { - name: 'unit.foot-pounds', - symbol: 'ft·lbf', - tags: ['torque','rotational force','foot-pound','foot-pounds','ft·lbf'] - }, - { - name: 'unit.inch-pounds', - symbol: 'in·lbf', - tags: ['torque','rotational force','inch-pounds','inch-pound','in·lbf'] - }, - { - name: 'unit.newton-per-meter', - symbol: 'N/m', - tags: ['linear density','force per unit length','newton per meter','N/m'] - }, - { - name: 'unit.atmospheres', - symbol: 'atm', - tags: ['pressure','force','compression','tension','atmosphere','atmospheres','atmospheric pressure','atm'] - }, - { - name: 'unit.pounds-per-square-inch', - symbol: 'psi', - tags: ['pressure','force','compression','tension','pounds per square inch','psi'] - }, - { - name: 'unit.torr', - symbol: 'Torr', - tags: ['pressure','force','compression','tension','vacuum pressure','torr'] - }, - { - name: 'unit.inches-of-mercury', - symbol: 'inHg', - tags: ['pressure','force','compression','tension','vacuum pressure','inHg','atmospheric pressure','barometric pressure'] - }, - { - name: 'unit.pascal-per-square-meter', - symbol: 'Pa/m²', - tags: ['pressure','stress','mechanical strength','pascal per square meter','Pa/m²'] - }, - { - name: 'unit.pound-per-square-inch', - symbol: 'psi/in²', - tags: ['pressure','stress','mechanical strength','pound per square inch','psi/in²'] - }, - { - name: 'unit.newton-per-square-meter', - symbol: 'N/m²', - tags: ['pressure','stress','mechanical strength','newton per square meter','N/m²'] - }, - { - name: 'unit.kilogram-force-per-square-meter', - symbol: 'kgf/m²', - tags: ['pressure','stress','mechanical strength','kilogram-force per square meter','kgf/m²'] - }, - { - name: 'unit.pascal-per-square-centimeter', - symbol: 'Pa/cm²', - tags: ['pressure','stress','mechanical strength','pascal per square centimeter','Pa/cm²'] - }, - { - name: 'unit.ton-force-per-square-inch', - symbol: 'tonf/in²', - tags: ['pressure','stress','mechanical strength','ton-force per square inch','tonf/in²'] - }, - { - name: 'unit.kilonewton-per-square-meter', - symbol: 'kN/m²', - tags: ['stress','pressure','mechanical strength','kilonewton per square meter','kN/m²'] - }, - { - name: 'unit.newton-per-square-millimeter', - symbol: 'N/mm²', - tags: ['stress','pressure','mechanical strength','newton per square millimeter','N/mm²'] - }, - { - name: 'unit.microjoule', - symbol: 'μJ', - tags: ['energy','microjoule','microjoules','μJ'] - }, - { - name: 'unit.millijoule', - symbol: 'mJ', - tags: ['energy','millijoule','millijoules','mJ'] - }, - { - name: 'unit.joule', - symbol: 'J', - tags: ['joule','joules','energy','work done','heat','electricity','mechanical work'] - }, - { - name: 'unit.kilojoule', - symbol: 'kJ', - tags: ['energy','kilojoule','kilojoules','kJ'] - }, - { - name: 'unit.megajoule', - symbol: 'MJ', - tags: ['energy','megajoule','megajoules','MJ'] - }, - { - name: 'unit.gigajoule', - symbol: 'GJ', - tags: ['energy','gigajoule','gigajoules','GJ'] - }, - { - name: 'unit.watt-hour', - symbol: 'Wh', - tags: ['energy','watt-hour','watt-hours','energy usage','power consumption','energy consumption','electricity usage'] - }, - { - name: 'unit.kilowatt-hour', - symbol: 'kWh', - tags: ['energy','kilowatt-hour','kilowatt-hours','energy usage','power consumption','energy consumption','electricity usage'] - }, - { - name: 'unit.electron-volts', - symbol: 'eV', - tags: ['energy','subatomic particles','radiation'] - }, - { - name: 'unit.joules-per-coulomb', - symbol: 'J/C', - tags: ['electrical potential energy','voltage','joules per coulomb','J/C'] - }, - { - name: 'unit.british-thermal-unit', - symbol: 'BTU', - tags: ['energy','heat','work done','british thermal unit','british thermal units','BTU'] - }, - { - name: 'unit.foot-pound', - symbol: 'ft·lb', - tags: ['energy','foot-pound','foot-pounds','ft·lb','ft⋅lbf'] - }, - { - name: 'unit.calorie', - symbol: 'Cal', - tags: ['energy','food energy','Calorie','Calories','Cal'] - }, - { - name: 'unit.small-calorie', - symbol: 'cal', - tags: ['energy','small calorie','calories','cal'] - }, - { - name: 'unit.kilocalorie', - symbol: 'kcal', - tags: ['energy','small calorie','kilocalories','kcal'] - }, - { - name: 'unit.joule-per-kelvin', - symbol: 'J/K', - tags: ['specific heat capacity','heat capacity per unit temperature','joule per kelvin','J/K'] - }, - { - name: 'unit.joule-per-kilogram-kelvin', - symbol: 'J/(kg·K)', - tags: ['specific heat capacity','heat capacity per unit mass and temperature','joule per kilogram-kelvin','J/(kg·K)'] - }, - { - name: 'unit.joule-per-kilogram', - symbol: 'J/kg', - tags: ['specific energy','specific energy capacity','joule per kilogram','J/kg'] - }, - { - name: 'unit.watt-per-meter-kelvin', - symbol: 'W/(m·K)', - tags: ['thermal conductivity','watt per meter-kelvin','W/(m·K)'] - }, - { - name: 'unit.joule-per-cubic-meter', - symbol: 'J/m³', - tags: ['energy density','joule per cubic meter','J/m³'] - }, - { - name: 'unit.therm', - symbol: 'thm', - tags: ['energy','natural gas consumption','BTU','therm','thm'] - }, - { - name: 'unit.electric-dipole-moment', - symbol: 'C·m', - tags: ['electric dipole','dipole moment','coulomb meter','C·m'] - }, - { - name: 'unit.magnetic-dipole-moment', - symbol: 'A·m²', - tags: ['magnetic dipole','dipole moment','ampere square meter','A·m²'] - }, - { - name: 'unit.debye', - symbol: 'D', - tags: ['polarization','electric dipole moment','debye','D'] - }, - { - name: 'unit.coulomb-per-square-meter-per-volt', - symbol: 'C·m²/V', - tags: ['polarization','electric field','coulomb per square meter per volt','C·m²/V'] - }, - { - name: 'unit.milliwatt', - symbol: 'mW', - tags: ['power','horsepower','performance','milliwatt','milliwatts','electricity','mW'] - }, - { - name: 'unit.microwatt', - symbol: 'μW', - tags: ['power','horsepower','performance','microwatt','microwatts','electricity','μW'] - }, - { - name: 'unit.watt', - symbol: 'W', - tags: ['power','horsepower','performance','watt','watts','electricity','W'] - }, - { - name: 'unit.kilowatt', - symbol: 'kW', - tags: ['power','horsepower','performance','kilowatt','kilowatts','electricity','kW'] - }, - { - name: 'unit.megawatt', - symbol: 'MW', - tags: ['power','horsepower','performance','megawatt','megawatts','electricity','MW'] - }, - { - name: 'unit.gigawatt', - symbol: 'GW', - tags: ['power','horsepower','performance','gigawatt','gigawatts','electricity','GW'] - }, - { - name: 'unit.metric-horsepower', - symbol: 'PS', - tags: ['power','performance','metric horsepower','PS'] - }, - { - name: 'unit.milliwatt-per-square-centimeter', - symbol: 'mW/cm²', - tags: ['power density','radiation intensity','sunlight intensity','signal power','intensity', - 'milliwatts per square centimeter','UV Intensity','mW/cm²'] - }, - { - name: 'unit.watt-per-square-centimeter', - symbol: 'W/cm²', - tags: ['power density','intensity of power','watts per square centimeter','W/cm²'] - }, - { - name: 'unit.kilowatt-per-square-centimeter', - symbol: 'kW/cm²', - tags: ['power density','intensity of power','kilowatts per square centimeter','kW/cm²'] - }, - { - name: 'unit.milliwatt-per-square-meter', - symbol: 'mW/m²', - tags: ['power density','intensity of power','milliwatts per square meter','mW/m²'] - }, - { - name: 'unit.watt-per-square-meter', - symbol: 'W/m²', - tags: ['power density','intensity of power','watts per square meter','W/m²'] - }, - { - name: 'unit.kilowatt-per-square-meter', - symbol: 'kW/m²', - tags: ['power density','intensity of power','kilowatts per square meter','kW/m²'] - }, - { - name: 'unit.watt-per-square-inch', - symbol: 'W/in²', - tags: ['power density','intensity of power','watts per square inch','W/in²'] - }, - { - name: 'unit.kilowatt-per-square-inch', - symbol: 'kW/in²', - tags: ['power density','intensity of power','kilowatts per square inch','kW/in²'] - }, - { - name: 'unit.horsepower', - symbol: 'hp', - tags: ['power','horsepower','performance','electricity','horsepowers','hp'] - }, - { - name: 'unit.btu-per-hour', - symbol: 'BTU/h', - tags: ['power','heat transfer','thermal energy','HVAC','BTU/h'] - }, - { - name: 'unit.coulomb', - symbol: 'C', - tags: ['charge','electricity','electrostatics','Coulomb','C'] - }, - { - name: 'unit.millicoulomb', - symbol: 'mC', - tags: ['charge','electricity','electrostatics','millicoulombs','mC'] - }, - { - name: 'unit.microcoulomb', - symbol: 'µC', - tags: ['charge','electricity','electrostatics','microcoulomb','µC'] - }, - { - name: 'unit.picocoulomb', - symbol: 'pC', - tags: ['charge','electricity','electrostatics','picocoulomb','pC'] - }, - { - name: 'unit.coulomb-per-meter', - symbol: 'C/m', - tags: ['electric displacement field per length','coulomb per meter','C/m'] - }, - { - name: 'unit.coulomb-per-cubic-meter', - symbol: 'C/m³', - tags: ['electric charge density','coulomb per cubic meter','C/m³'] - }, - { - name: 'unit.coulomb-per-square-meter', - symbol: 'C/m²', - tags: ['electric surface charge density','coulomb per square meter','C/m²'] - }, - { - name: 'unit.square-millimeter', - symbol: 'mm²', - tags: ['area','lot','zone','space','region','square millimeter','square millimeters','mm²','sq-mm'] - }, - { - name: 'unit.square-centimeter', - symbol: 'cm²', - tags: ['area','lot','zone','space','region','square centimeter','square centimeters','cm²','sq-cm'] - }, - { - name: 'unit.square-meter', - symbol: 'm²', - tags: ['area','lot','zone','space','region','square meter','square meters','m²','sq-m'] - }, - { - name: 'unit.hectare', - symbol: 'ha', - tags: ['area','lot','zone','space','region','hectare','hectares','ha'] - }, - { - name: 'unit.square-kilometer', - symbol: 'km²', - tags: ['area','lot','zone','space','region','square kilometer','square kilometers','km²','sq-km'] - }, - { - name: 'unit.square-inch', - symbol: 'in²', - tags: ['area','lot','zone','space','region','square inch','square inches','in²','sq-in'] - }, - { - name: 'unit.square-foot', - symbol: 'ft²', - tags: ['area','lot','zone','space','region','square foot','square feet','ft²','sq-ft'] - }, - { - name: 'unit.square-yard', - symbol: 'yd²', - tags: ['area','lot','zone','space','region','square yard','square yards','yd²','sq-yd'] - }, - { - name: 'unit.acre', - symbol: 'a', - tags: ['area','lot','zone','space','region','acre','acres','a'] - }, - { - name: 'unit.square-mile', - symbol: 'ml²', - tags: ['area','lot','zone','space','region','square mile','square miles','ml²','sq-mi'] - }, - { - name: 'unit.are', - symbol: 'are', - tags: ['area','land measurement','are'] - }, - { - name: 'unit.barn', - symbol: 'barn', - tags: ['cross-sectional area','particle physics','nuclear physics','barn'] - }, - { - name: 'unit.circular-inch', - symbol: 'circin', - tags: ['area','circular measurement','circular inch','circin'] - }, - { - name: 'unit.milliampere-hour', - symbol: 'mAh', - tags: ['electric current','current flow','electric charge','current capacity','flow of electricity', - 'electrical flow','milliampere-hour','milliampere-hours','mAh'] - }, - { - name: 'unit.ampere-hours', - symbol: 'Ah', - tags: ['electric current','current flow','electric charge','current capacity','flow of electricity', - 'electrical flow','ampere','ampere-hours','Ah'] - }, - { - name: 'unit.kiloampere-hours', - symbol: 'kAh', - tags: ['electric current','current flow','electric charge','current capacity','flow of electricity','electrical flow', - 'kiloampere-hours','kiloampere-hour','kAh'] - }, - { - name: 'unit.nanoampere', - symbol: 'nA', - tags: ['current','amperes','nanoampere','nA'] - }, - { - name: 'unit.picoampere', - symbol: 'pA', - tags: ['current','amperes','picoampere','pA'] - }, - { - name: 'unit.microampere', - symbol: 'μA', - tags: ['electric current','microampere','microamperes','μA'] - }, - { - name: 'unit.milliampere', - symbol: 'mA', - tags: ['electric current','milliampere','milliamperes','mA'] - }, - { - name: 'unit.ampere', - symbol: 'A', - tags: ['electric current','current flow','flow of electricity','electrical flow','ampere','amperes','amperage','A'] - }, - { - name: 'unit.kiloamperes', - symbol: 'kA', - tags: ['electric current','current flow','kiloamperes','kA'] - }, - { - name: 'unit.microampere-per-square-centimeter', - symbol: 'µA/cm²', - tags: ['Current density','microampere per square centimeter','µA/cm²'] - }, - { - name: 'unit.ampere-per-square-meter', - symbol: 'A/m²', - tags: ['current density','current per unit area','ampere per square meter','A/m²'] - }, - { - name: 'unit.ampere-per-meter', - symbol: 'A/m', - tags: ['magnetic field strength','magnetic field intensity','ampere per meter','A/m'] - }, - { - name: 'unit.oersted', - symbol: 'Oe', - tags: ['magnetic field','oersted','Oe'] - }, - { - name: 'unit.bohr-magneton', - symbol: 'μB', - tags: ['atomic physics','magnetic moment','bohr magneton','μB'] - }, - { - name: 'unit.ampere-meter-squared', - symbol: 'A·m²', - tags: ['magnetic moment','dipole moment','ampere-meter squared','A·m²'] - }, - { - name: 'unit.ampere-meter', - symbol: 'A·m', - tags: ['magnetic field','current loop','ampere-meter','A·m'] - }, - { - name: 'unit.nanovolt', - symbol: 'nV', - tags: ['voltage','volts','nanovolt','nV'] - }, - { - name: 'unit.picovolt', - symbol: 'pV', - tags: ['voltage','volts','picovolt','pV'] - }, - { - name: 'unit.millivolts', - symbol: 'mV', - tags: ['electric potential','electric tension','voltage','millivolt','millivolts','mV'] - }, - { - name: 'unit.microvolts', - symbol: 'μV', - tags: ['electric potential','electric tension','voltage','microvolt','microvolts','μV'] - }, - { - name: 'unit.volt', - symbol: 'V', - tags: ['electric potential','electric tension','voltage','volt','volts','V','power source','battery','battery level'] - }, - { - name: 'unit.kilovolts', - symbol: 'kV', - tags: ['electric potential','electric tension','voltage','kilovolt','kilovolts','kV'] - }, - { - name: 'unit.dbmV', - symbol: 'dBmV', - tags: ['decibels millivolt','voltage level','signal','dBmV'] - }, - { - name: 'unit.volt-meter', - symbol: 'V·m', - tags: ['electric flux','volt-meter','V·m'] - }, - { - name: 'unit.kilovolt-meter', - symbol: 'kV·m', - tags: ['electric flux','kilovolt-meter','kV·m'] - }, - { - name: 'unit.megavolt-meter', - symbol: 'MV·m', - tags: ['electric flux','megavolt-meter','MV·m'] - }, - { - name: 'unit.microvolt-meter', - symbol: 'µV·m', - tags: ['electric flux','microvolt-meter','µV·m'] - }, - { - name: 'unit.millivolt-meter', - symbol: 'mV·m', - tags: ['electric flux','millivolt-meter','mV·m'] - }, - { - name: 'unit.nanovolt-meter', - symbol: 'nV·m', - tags: ['electric flux','nanovolt-meter','nV·m'] - }, - { - name: 'unit.ohm', - symbol: 'Ω', - tags: ['electrical resistance','resistance','impedance','ohm'] - }, - { - name: 'unit.microohm', - symbol: 'μΩ', - tags: ['electrical resistance','resistance','microohm','μΩ'] - }, - { - name: 'unit.milliohm', - symbol: 'mΩ', - tags: ['electrical resistance','resistance','milliohm','mΩ'] - }, - { - name: 'unit.kilohm', - symbol: 'kΩ', - tags: ['electrical resistance','resistance','kilohm','kΩ'] - }, - { - name: 'unit.megohm', - symbol: 'MΩ', - tags: ['electrical resistance','resistance','megohm','MΩ'] - }, - { - name: 'unit.gigohm', - symbol: 'GΩ', - tags: ['electrical resistance','resistance','gigohm','GΩ'] - }, - { - name: 'unit.hertz', - symbol: 'Hz', - tags: ['frequency','cycles per second','hertz','Hz'] - }, - { - name: 'unit.kilohertz', - symbol: 'kHz', - tags: ['frequency','cycles per second','kilohertz','kHz'] - }, - { - name: 'unit.megahertz', - symbol: 'MHz', - tags: ['frequency','cycles per second','megahertz','MHz'] - }, - { - name: 'unit.gigahertz', - symbol: 'GHz', - tags: ['frequency','cycles per second','gigahertz','GHz'] - }, - { - name: 'unit.rpm', - symbol: 'RPM', - tags: ['speed','velocity','cycle','engine','Revolutions Per Minute','RPM','angular velocity','rotation speed'] - }, - { - name: 'unit.candela-per-square-meter', - symbol: 'cd/m²', - tags: ['brightness','light level','Luminance','Candela per square meter','cd/m²'] - }, - { - name: 'unit.candela', - symbol: 'cd', - tags: ['light intensity','candle power','luminous intensity','Candela','cd'] - }, - { - name: 'unit.lumen', - symbol: 'lm', - tags: ['total light output','light power','luminous flux','Lumen','lm'] - }, - { - name: 'unit.lux', - symbol: 'lx', - tags: ['illumination','light level on a surface','illuminance','Lux','lx'] - }, - { - name: 'unit.foot-candle', - symbol: 'fc', - tags: ['illuminance','light level','foot-candle','fc'] - }, - { - name: 'unit.lumen-per-square-meter', - symbol: 'lm/m²', - tags: ['illuminance','light level','lumen per square meter','lm/m²'] - }, - { - name: 'unit.lux-second', - symbol: 'lx·s', - tags: ['light exposure','illumination time','light dosage','Lux second','lx·s'] - }, - { - name: 'unit.lumen-second', - symbol: 'lm·s', - tags: ['total light energy','luminous energy','Lumen second','lm·s'] - }, - { - name: 'unit.lumens-per-watt', - symbol: 'lm/W', - tags: ['lighting efficiency','light output per energy','luminous efficacy','Lumens per watt','lm/W'] - }, - { - name: 'unit.absorbance', - symbol: 'AU', - tags: ['optical density','light absorption','absorbance','AU'] - }, - { - name: 'unit.mole', - symbol: 'mol', - tags: ['amount of substance','substance quantity','mole','moles','mol'] - }, - { - name: 'unit.nanomole', - symbol: 'nmol', - tags: ['amount of substance','substance quantity','concentration','nanomole','nmol'] - }, - { - name: 'unit.micromole', - symbol: 'μmol', - tags: ['amount of substance','substance quantity','micromole','μmol'] - }, - { - name: 'unit.millimole', - symbol: 'mmol', - tags: ['amount of substance','substance quantity','millimole','mmol'] - }, - { - name: 'unit.kilomole', - symbol: 'kmol', - tags: ['amount of substance','substance quantity','kilomole','kmol'] - }, - { - name: 'unit.mole-per-cubic-meter', - symbol: 'mol/m³', - tags: ['concentration','amount of substance','mole per cubic meter','mol/m³'] - }, - { - name: 'unit.battery', - symbol: '%', - tags: ['power source','state of charge (SoC)','battery','battery level','level','humidity','moisture', - 'relative humidity','water content','soil moisture','irrigation','water in soil','soil water content','VWC', - 'Volumetric Water Content','Total Harmonic Distortion','THD','power quality','UV Transmittance','%'] - }, - { - name: 'unit.rssi', - symbol: 'rssi', - tags: ['signal strength','signal level','received signal strength indicator','rssi','dBm'] - }, - { - name: 'unit.ppm', - symbol: 'ppm', - tags: ['carbon dioxide','co²','carbon monoxide','co','aqi','air quality','total volatile organic compounds','tvoc','ppm'] - }, - { - name: 'unit.ppb', - symbol: 'ppb', - tags: ['ozone','o³','nitrogen dioxide','no²','sulfur dioxide','so²','aqi','air quality','tvoc','ppb'] - }, - { - name: 'unit.micrograms-per-cubic-meter', - symbol: 'µg/m³', - tags: ['coarse particulate matter','pm10','fine particulate matter','pm2.5','aqi','air quality', - 'total volatile organic compounds','tvoc','micrograms per cubic meter','µg/m³'] - }, - { - name: 'unit.aqi', - symbol: 'aqi', - tags: ['AQI','air quality index'] - }, - { - name: 'unit.gram-per-cubic-meter', - symbol: 'g/m³', - tags: ['humidity','moisture','absolute humidity','g/m³'] - }, - { - name: 'unit.gram-per-kilogram', - symbol: 'g/kg', - tags: ['humidity','moisture','specific humidity','g/kg'] - }, - { - name: 'unit.millimeters-per-second', - symbol: 'mm/s', - tags: ['velocity','speed','rate of motion','peak','peak to peak','root mean square (RMS)','vibration','mm/s'] - }, - { - name: 'unit.neper', - symbol: 'Np', - tags: ['logarithmic unit','ratio','gain','loss','attenuation','neper','Np'] - }, - { - name: 'unit.bel', - symbol: 'B', - tags: ['logarithmic unit','power ratio','intensity ratio','bel','B'] - }, - { - name: 'unit.decibel', - symbol: 'dB', - tags: ['noise level','sound level','volume','acoustics','decibel','dB'] - }, - { - name: 'unit.meters-per-second-squared', - symbol: 'm/s²', - tags: ['peak','peak to peak','root mean square (RMS)','vibration','meters per second squared','m/s²'] - }, - { - name: 'unit.becquerel', - symbol: 'Bq', - tags: ['radioactivity','radiation','becquerel','Bq'] - }, - { - name: 'unit.curie', - symbol: 'Ci', - tags: ['radioactivity','radiation','curie','Ci'] - }, - { - name: 'unit.gray', - symbol: 'Gy', - tags: ['radiation dose','gray','Gy'] - }, - { - name: 'unit.sievert', - symbol: 'Sv', - tags: ['radiation dose','sievert','radiation dose equivalent2','Sv'] - }, - { - name: 'unit.roentgen', - symbol: 'R', - tags: ['radiation exposure','roentgen','R'] - }, - { - name: 'unit.cps', - symbol: 'cps', - tags: ['radiation detection','counts per second','cps'] - }, - { - name: 'unit.rad', - symbol: 'Rad', - tags: ['radiation dose','rad'] - }, - { - name: 'unit.rem', - symbol: 'Rem', - tags: ['radiation dose equivalent','rem'] - }, - { - name: 'unit.dps', - symbol: 'dps', - tags: ['radioactive decay','radioactivity','disintegrations per second','dps'] - }, - { - name: 'unit.rutherford', - symbol: 'Rd', - tags: ['radioactive decay','radioactivity','rutherford','Rd'] - }, - { - name: 'unit.coulombs-per-kilogram', - symbol: 'C/kg', - tags: ['radiation exposure','dose','coulombs per kilogram','electric charge-to-mass ratio','C/kg'] - }, - { - name: 'unit.becquerels-per-cubic-meter', - symbol: 'Bq/m³', - tags: ['radioactivity','radiation','becquerels per cubic meter','Bq/m³'] - }, - { - name: 'unit.curies-per-liter', - symbol: 'Ci/L', - tags: ['radioactivity','radiation','curies per liter','Ci/L'] - }, - { - name: 'unit.becquerels-per-second', - symbol: 'Bq/s', - tags: ['radioactive decay rate','becquerels per second','Bq/s'] - }, - { - name: 'unit.curies-per-second', - symbol: 'Ci/s', - tags: ['radioactive decay rate','curies per second','Ci/s'] - }, - { - name: 'unit.gy-per-second', - symbol: 'Gy/s', - tags: ['absorbed dose rate','radiation dose rate','gray per second','Gy/s'] - }, - { - name: 'unit.watt-per-steradian', - symbol: 'W/sr', - tags: ['radiant intensity','power per unit solid angle','watt per steradian','W/sr'] - }, - { - name: 'unit.watt-per-square-metre-steradian', - symbol: 'W/(m²·sr)', - tags: ['radiance','radiant flux density','watt per square metre-steradian','W/(m²·sr)'] - }, - { - name: 'unit.ph-level', - symbol: 'pH', - tags: ['acidity','alkalinity','neutral','acid','base','pH','soil pH','water quality','water pH'] - }, - { - name: 'unit.turbidity', - symbol: 'NTU', - tags: ['water turbidity','water clarity','Nephelometric Turbidity Units','NTU'] - }, - { - name: 'unit.mg-per-liter', - symbol: 'mg/L', - tags: ['dissolved oxygen','water quality','mg/L'] - }, - { - name: 'unit.microsiemens-per-centimeter', - symbol: 'µS/cm', - tags: ['Electrical conductivity','water quality','soil quality','microsiemens per centimeter','µS/cm'] - }, - { - name: 'unit.millisiemens-per-meter', - symbol: 'mS/m', - tags: ['Electrical conductivity','water quality','soil quality','millisiemens per meter','mS/m'] - }, - { - name: 'unit.siemens-per-meter', - symbol: 'S/m', - tags: ['Electrical conductivity','water quality','soil quality','siemens per meter','S/m'] - }, - { - name: 'unit.kilogram-per-cubic-meter', - symbol: 'kg/m³', - tags: ['density','mass per unit volume','kg/m³'] - }, - { - name: 'unit.gram-per-cubic-centimeter', - symbol: 'g/cm³', - tags: ['density','mass per unit volume','g/cm³'] - }, - { - name: 'unit.kilogram-per-square-meter', - symbol: 'kg/m²', - tags: ['density','surface density','areal density','mass per unit area','kg/m²'] - }, - { - name: 'unit.milligram-per-milliliter', - symbol: 'mg/mL', - tags: ['concentration','mass per volume','mg/mL'] - }, - { - name: 'unit.pound-per-cubic-foot', - symbol: 'lb/ft³', - tags: ['Density','mass per unit volume','lb/ft³'] - }, - { - name: 'unit.ounces-per-cubic-inch', - symbol: 'oz/in³', - tags: ['density','mass per unit volume','oz/in³'] - }, - { - name: 'unit.tons-per-cubic-yard', - symbol: 'ton/yd³', - tags: ['density','mass per unit volume','ton/yd³'] - }, - { - name: 'unit.particle-density', - symbol: 'particles/mL', - tags: ['particle concentration','count','particles/mL'] - }, - { - name: 'unit.kilometers-per-liter', - symbol: 'km/L', - tags: ['fuel efficiency','km/L'] - }, - { - name: 'unit.miles-per-gallon', - symbol: 'mpg', - tags: ['fuel efficiency','mpg'] - }, - { - name: 'unit.liters-per-100-km', - symbol: 'L/100km', - tags: ['fuel efficiency','L/100km'] - }, - { - name: 'unit.gallons-per-mile', - symbol: 'gal/mi', - tags: ['fuel efficiency','gal/mi'] - }, - { - name: 'unit.liters-per-hour', - symbol: 'L/hr', - tags: ['fuel consumption','L/hr'] - }, - { - name: 'unit.gallons-per-hour', - symbol: 'gal/hr', - tags: ['fuel consumption','gal/hr'] - }, - { - name: 'unit.beats-per-minute', - symbol: 'bpm', - tags: ['heart rate','pulse','bpm'] - }, - { - name: 'unit.millimeters-of-mercury', - symbol: 'mmHg', - tags: ['blood pressure','systolic','diastolic','mmHg'] - }, - { - name: 'unit.milligrams-per-deciliter', - symbol: 'mg/dL', - tags: ['glucose','blood sugar','glucose level','mg/dL'] - }, - { - name: 'unit.g-force', - symbol: 'G', - tags: ['acceleration','gravity','force','g-load','G'] - }, - { - name: 'unit.kilonewton', - symbol: 'kN', - tags: ['force','kN'] - }, - { - name: 'unit.kilogram-force', - symbol: 'kgf', - tags: ['force','kgf'] - }, - { - name: 'unit.pound-force', - symbol: 'lbf', - tags: ['force','lbf'] - }, - { - name: 'unit.kilopound-force', - symbol: 'klbf', - tags: ['force','klbf'] - }, - { - name: 'unit.dyne', - symbol: 'dyn', - tags: ['force','dyn'] - }, - { - name: 'unit.poundal', - symbol: 'pdl', - tags: ['force','pdl'] - }, - { - name: 'unit.kip', - symbol: 'kip', - tags: ['force','kip'] - }, - { - name: 'unit.gal', - symbol: 'Gal', - tags: ['acceleration','gravity','g-force','Gal'] - }, - { - name: 'unit.gravity', - symbol: 'gravity', - tags: ['acceleration','gravity','g-force'] - }, - { - name: 'unit.hectopascal', - symbol: 'hPa', - tags: ['atmospheric pressure','air pressure','weather','altitude','flight','hPa'] - }, - { - name: 'unit.atmosphere', - symbol: 'atm', - tags: ['atmospheric pressure','air pressure','weather','altitude','flight','atm'] - }, - { - name: 'unit.millibars', - symbol: 'mb', - tags: ['atmospheric pressure','air pressure','weather','altitude','flight','mb'] - }, - { - name: 'unit.inch-of-mercury', - symbol: 'inHg', - tags: ['atmospheric pressure','air pressure','weather','altitude','flight','inHg','richter'] - }, - { - name: 'unit.richter-scale', - symbol: 'richter', - tags: ['earthquake','seismic activity','richter'] - }, - { - name: 'unit.percentage', - symbol: '%', - tags: ['percentage'] - }, - { - name: 'unit.second', - symbol: 's', - tags: ['time','duration','interval','angle','second','arcsecond','sec'] - }, - { - name: 'unit.minute', - symbol: 'min', - tags: ['time','duration','interval','angle','minute','arcminute','min'] - }, - { - name: 'unit.hour', - symbol: 'h', - tags: ['time','duration','interval','h'] - }, - { - name: 'unit.day', - symbol: 'd', - tags: ['time','duration','interval','d'] - }, - { - name: 'unit.week', - symbol: 'wk', - tags: ['time','duration','interval','wk'] - }, - { - name: 'unit.month', - symbol: 'mo', - tags: ['time','duration','interval','mo'] - }, - { - name: 'unit.year', - symbol: 'yr', - tags: ['time','duration','interval','yr'] - }, - { - name: 'unit.cubic-foot-per-minute', - symbol: 'ft³/min', - tags: ['airflow','ventilation','HVAC','gas flow rate','CFM','flow rate','fluid flow','cubic foot per minute','ft³/min'] - }, - { - name: 'unit.cubic-meters-per-hour', - symbol: 'm³/hr', - tags: ['airflow','ventilation','HVAC','gas flow rate','cubic meters per hour','m³/hr'] - }, - { - name: 'unit.cubic-meters-per-second', - symbol: 'm³/s', - tags: ['airflow','ventilation','HVAC','gas flow rate','cubic meters per second','m³/s'] - }, - { - name: 'unit.liter-per-second', - symbol: 'L/s', - tags: ['airflow','ventilation','HVAC','gas flow rate','liter per second','L/s'] - }, - { - name: 'unit.liter-per-minute', - symbol: 'L/min', - tags: ['airflow','ventilation','HVAC','gas flow rate','liter per minute','L/min'] - }, - { - name: 'unit.gallons-per-minute', - symbol: 'GPM', - tags: ['airflow','ventilation','HVAC','gas flow rate','gallons per minute','GPM'] - }, - { - name: 'unit.cubic-foot-per-second', - symbol: 'ft³/s', - tags: ['flow rate','fluid flow','cubic foot per second','cubic feet per second','ft³/s'] - }, - { - name: 'unit.milliliters-per-minute', - symbol: 'mL/min', - tags: ['Flow rate','fluid dynamics','milliliters per minute','mL/min'] - }, - { - name: 'unit.bit', - symbol: 'bit', - tags: ['data','binary digit','information','bit'] - }, - { - name: 'unit.byte', - symbol: 'B', - tags: ['data','byte','information','storage','memory','B'] - }, - { - name: 'unit.kilobyte', - symbol: 'KB', - tags: ['data','kilobyte','KB'] - }, - { - name: 'unit.megabyte', - symbol: 'MB', - tags: ['data','megabyte','MB'] - }, - { - name: 'unit.gigabyte', - symbol: 'GB', - tags: ['data','gigabyte','GB'] - }, - { - name: 'unit.terabyte', - symbol: 'TB', - tags: ['data','terabyte','TB'] - }, - { - name: 'unit.petabyte', - symbol: 'PB', - tags: ['data','petabyte','PB'] - }, - { - name: 'unit.exabyte', - symbol: 'EB', - tags: ['data','exabyte','EB'] - }, - { - name: 'unit.zettabyte', - symbol: 'ZB', - tags: ['data','zettabyte','ZB'] - }, - { - name: 'unit.yottabyte', - symbol: 'YB', - tags: ['data','yottabyte','YB'] - }, - { - name: 'unit.bit-per-second', - symbol: 'bps', - tags: ['data transfer rate','bps'] - }, - { - name: 'unit.kilobit-per-second', - symbol: 'kbps', - tags: ['data transfer rate','kbps'] - }, - { - name: 'unit.megabit-per-second', - symbol: 'Mbps', - tags: ['data transfer rate','Mbps'] - }, - { - name: 'unit.gigabit-per-second', - symbol: 'Gbps', - tags: ['data transfer rate','Gbps'] - }, - { - name: 'unit.terabit-per-second', - symbol: 'Tbps', - tags: ['data transfer rate','Tbps'] - }, - { - name: 'unit.byte-per-second', - symbol: 'B/s', - tags: ['data transfer rate','B/s'] - }, - { - name: 'unit.kilobyte-per-second', - symbol: 'KB/s', - tags: ['data transfer rate','KB/s'] - }, - { - name: 'unit.megabyte-per-second', - symbol: 'MB/s', - tags: ['data transfer rate','MB/s'] - }, - { - name: 'unit.gigabyte-per-second', - symbol: 'GB/s', - tags: ['data transfer rate','GB/s'] - }, - { - name: 'unit.degree', - symbol: 'deg', - tags: ['angle','degree','degrees','deg'] - }, - { - name: 'unit.radian', - symbol: 'rad', - tags: ['angle','radian','radians','rad'] - }, - { - name: 'unit.gradian', - symbol: 'grad', - tags: ['angle','gradian','grades','grad'] - }, - { - name: 'unit.mil', - symbol: 'mil', - tags: ['angle','military angle','angular mil','mil'] - }, - { - name: 'unit.revolution', - symbol: 'rev', - tags: ['angle','revolution','full circle','complete turn','rev'] - }, - { - name: 'unit.siemens', - symbol: 'S', - tags: ['electrical conductance','conductance','siemens','S'] - }, - { - name: 'unit.millisiemens', - symbol: 'mS', - tags: ['electrical conductance','conductance','millisiemens','mS'] - }, - { - name: 'unit.microsiemens', - symbol: 'μS', - tags: ['electrical conductance','conductance','microsiemens','μS'] - }, - { - name: 'unit.kilosiemens', - symbol: 'kS', - tags: ['electrical conductance','conductance','kilosiemens','kS'] - }, - { - name: 'unit.megasiemens', - symbol: 'MS', - tags: ['electrical conductance','conductance','megasiemens','MS'] - }, - { - name: 'unit.gigasiemens', - symbol: 'GS', - tags: ['electrical conductance','conductance','gigasiemens','GS'] - }, - { - name: 'unit.farad', - symbol: 'F', - tags: ['electric capacitance','capacitance','farad','F'] - }, - { - name: 'unit.millifarad', - symbol: 'mF', - tags: ['electric capacitance','capacitance','millifarad','mF'] - }, - { - name: 'unit.microfarad', - symbol: 'μF', - tags: ['electric capacitance','capacitance','microfarad','μF'] - }, - { - name: 'unit.nanofarad', - symbol: 'nF', - tags: ['electric capacitance','capacitance','nanofarad','nF'] - }, - { - name: 'unit.picofarad', - symbol: 'pF', - tags: ['electric capacitance','capacitance','picofarad','pF'] - }, - { - name: 'unit.kilofarad', - symbol: 'kF', - tags: ['electric capacitance','capacitance','kilofarad','kF'] - }, - { - name: 'unit.megafarad', - symbol: 'MF', - tags: ['electric capacitance','capacitance','megafarad','MF'] - }, - { - name: 'unit.gigafarad', - symbol: 'GF', - tags: ['electric capacitance','capacitance','gigafarad','GF'] - }, - { - name: 'unit.terfarad', - symbol: 'TF', - tags: ['electric capacitance','capacitance','terafarad','TF'] - }, - { - name: 'unit.farad-per-meter', - symbol: 'F/m', - tags: ['electric permittivity','farad per meter','F/m'] - }, - { - name: 'unit.tesla', - symbol: 'T', - tags: ['magnetic field','magnetic field strength','tesla','T','magnetic flux density'] - }, - { - name: 'unit.gauss', - symbol: 'G', - tags: ['magnetic field','magnetic field strength','gauss','G','magnetic flux density'] - }, - { - name: 'unit.kilogauss', - symbol: 'kG', - tags: ['magnetic field','magnetic field strength','kilogauss','kG','magnetic flux density'] - }, - { - name: 'unit.millitesla', - symbol: 'mT', - tags: ['magnetic field','magnetic field strength','millitesla','mT'] - }, - { - name: 'unit.microtesla', - symbol: 'μT', - tags: ['magnetic field','magnetic field strength','microtesla','μT'] - }, - { - name: 'unit.nanotesla', - symbol: 'nT', - tags: ['magnetic field','magnetic field strength','nanotesla','nT'] - }, - { - name: 'unit.kilotesla', - symbol: 'kT', - tags: ['magnetic field','magnetic field strength','kilotesla','kT'] - }, - { - name: 'unit.megatesla', - symbol: 'MT', - tags: ['magnetic field','magnetic field strength','megatesla','MT'] - }, - { - name: 'unit.millitesla-square-meters', - symbol: 'millitesla square meters', - tags: ['magnetic field','millitesla square meters'] - }, - { - name: 'unit.gamma', - symbol: 'γ', - tags: ['magnetic flux density','gamma','γ'] - }, - { - name: 'unit.lambda', - symbol: 'λ', - tags: ['wavelength','lambda','λ'] - }, - { - name: 'unit.square-meter-per-second', - symbol: 'm²/s', - tags: ['kinematic viscosity','m²/s'] - }, - { - name: 'unit.square-centimeter-per-second', - symbol: 'cm²/s', - tags: ['kinematic viscosity','cm²/s'] - }, - { - name: 'unit.stoke', - symbol: 'St', - tags: ['kinematic viscosity','stokes','St'] - }, - { - name: 'unit.centistokes', - symbol: 'cSt', - tags: ['kinematic viscosity','centistokes','cSt'] - }, - { - name: 'unit.square-foot-per-second', - symbol: 'ft²/s', - tags: ['kinematic viscosity','ft²/s'] - }, - { - name: 'unit.square-inch-per-second', - symbol: 'in²/s', - tags: ['kinematic viscosity','in²/s'] - }, - { - name: 'unit.pascal-second', - symbol: 'Pa·s', - tags: ['dynamic viscosity','viscosity','fluid mechanics','pascal-second','Pa·s'] - }, - { - name: 'unit.centipoise', - symbol: 'cP', - tags: ['viscosity','dynamic viscosity','fluid viscosity','centipoise','cP'] - }, - { - name: 'unit.poise', - symbol: 'P', - tags: ['viscosity','dynamic viscosity','fluid viscosity','poise','P'] - }, - { - name: 'unit.reynolds', - symbol: 'Re', - tags: ['fluid flow regime','fluid mechanics','reynolds','Re'] - }, - { - name: 'unit.pound-per-foot-hour', - symbol: 'lb/(ft·h)', - tags: ['pound per foot-hour','lb/(ft·h)'] - }, - { - name: 'unit.newton-second-per-square-meter', - symbol: 'N·s/m²', - tags: ['newton second per square meter','N·s/m²'] - }, - { - name: 'unit.dyne-second-per-square-centimeter', - symbol: 'dyn·s/cm²', - tags: ['dyne second per square centimeter','dyn·s/cm²'] - }, - { - name: 'unit.kilogram-per-meter-second', - symbol: 'kg/(m·s)', - tags: ['kilogram per meter-second','kg/(m·s)'] - }, - { - name: 'unit.tesla-square-meters', - symbol: 'T/m²', - tags: ['magnetic flux density','tesla square meters','T/m²'] - }, - { - name: 'unit.maxwell', - symbol: 'Mx', - tags: ['magnetic flux','magnetic field','maxwell','Mx'] - }, - { - name: 'unit.tesla-per-meter', - symbol: 'T/m', - tags: ['magnetic field','tesla per meter','T/m'] - }, - { - name: 'unit.gauss-per-centimeter', - symbol: 'G/cm', - tags: ['magnetic field','gauss per centimeter','G/cm'] - }, - { - name: 'unit.weber', - symbol: 'Wb', - tags: ['magnetic flux','weber','Wb'] - }, - { - name: 'unit.microweber', - symbol: 'µWb', - tags: ['magnetic flux','microweber','µWb'] - }, - { - name: 'unit.milliweber', - symbol: 'mWb', - tags: ['magnetic flux','milliweber','mWb'] - }, - { - name: 'unit.gauss-square-centimeter', - symbol: 'G·cm²', - tags: ['magnetic flux','gauss-square centimeter','G·cm²'] - }, - { - name: 'unit.kilogauss-square-centimeter', - symbol: 'kG·cm²', - tags: ['magnetic flux','kilogauss-square centimeter','kG·cm²'] - }, - { - name: 'unit.henry', - symbol: 'H', - tags: ['inductance','magnetic induction','H'] - }, - { - name: 'unit.millihenry', - symbol: 'mH', - tags: ['inductance','millihenry','mH'] - }, - { - name: 'unit.microhenry', - symbol: 'µH', - tags: ['inductance','microhenry','µH'] - }, - { - name: 'unit.nanohenry', - symbol: 'nH', - tags: ['inductance','nanohenry','nH'] - }, - { - name: 'unit.henry-per-meter', - symbol: 'H/m', - tags: ['magnetic permeability','henry per meter','H/m'] - }, - { - name: 'unit.tesla-meter-per-ampere', - symbol: 'T·m/A', - tags: ['magnetic field','Tesla Meter per Ampere','T·m/A','magnetic flux'] - }, - { - name: 'unit.gauss-per-oersted', - symbol: 'G/Oe', - tags: ['magnetic field','Gauss per Oersted','G/Oe'] - }, - { - name: 'unit.kilogram-per-mole', - symbol: 'kg/mol', - tags: ['molar mass','kilogram per mole','kg/mol'] - }, - { - name: 'unit.gram-per-mole', - symbol: 'g/mol', - tags: ['molar mass','gram per mole','g/mol'] - }, - { - name: 'unit.milligram-per-mole', - symbol: 'mg/mol', - tags: ['molar mass','milligram per mole','mg/mol'] - }, - { - name: 'unit.joule-per-mole', - symbol: 'J/mol', - tags: ['molar energy','joule per mole','J/mol'] - }, - { - name: 'unit.joule-per-mole-kelvin', - symbol: 'J/(mol·K)', - tags: ['molar heat capacity','joule per mole-kelvin','J/(mol·K)'] - }, - { - name: 'unit.millivolts-per-meter', - symbol: 'mV/m', - tags: ['electric field strength','millivolts per meter','mV/m'] - }, - { - name: 'unit.volts-per-meter', - symbol: 'V/m', - tags: ['electric field strength','volts per meter','V/m'] - }, - { - name: 'unit.kilovolts-per-meter', - symbol: 'kV/m', - tags: ['electric field strength','kilovolts per meter','kV/m'] - }, - { - name: 'unit.radian-per-second', - symbol: 'rad/s', - tags: ['angular velocity','rotation speed','rad/s'] - }, - { - name: 'unit.radian-per-second-squared', - symbol: 'rad/s²', - tags: ['angular acceleration','rotation rate of change','rad/s²'] - }, - { - name: 'unit.revolutions-per-minute-per-second', - symbol: 'rpm/s', - tags: ['angular acceleration','rotation rate of change','rpm/s'] - }, - { - name: 'unit.revolutions-per-minute-per-second-squared', - symbol: 'rpm/s²', - tags: ['angular acceleration','rotation rate of change','rpm/s²'] - }, - { - name: 'unit.deg-per-second', - symbol: 'deg/s', - tags: ['angular velocity','degrees per second','deg/s'] - }, - { - name: 'unit.degrees-brix', - symbol: '°Bx', - tags: ['sugar content','fruit ripeness','Bx'] - }, - { - name: 'unit.katal', - symbol: 'kat', - tags: ['catalytic activity','enzyme activity','kat'] - }, - { - name: 'unit.katal-per-cubic-metre', - symbol: 'kat/m³', - tags: ['catalytic activity concentration','enzyme concentration','kat/m³'] - } -]; - -export const unitBySymbol = (symbol: string): Unit => units.find(u => u.symbol === symbol); +export const unitBySymbol = (_units: Array, symbol: string): Unit => _units.find(u => u.symbol === symbol); const searchUnitTags = (unit: Unit, searchText: string): boolean => !!unit.tags.find(t => t.toUpperCase().includes(searchText.toUpperCase())); diff --git a/ui-ngx/src/assets/model/units.json b/ui-ngx/src/assets/model/units.json new file mode 100644 index 0000000000..ecbda65cca --- /dev/null +++ b/ui-ngx/src/assets/model/units.json @@ -0,0 +1,2022 @@ +{ + "units": [ + { + "name": "unit.millimeter", + "symbol": "mm", + "tags": ["level","height","distance","length","width","gap","depth","millimeter","millimeters","rainfall","precipitation", + "displacement","position","movement","transition","mm"] + }, + { + "name": "unit.centimeter", + "symbol": "cm", + "tags": ["level","height","distance","length","width","gap","depth","centimeter","centimeters","rainfall","precipitation", + "displacement","position","movement","transition","cm"] + }, + { + "name": "unit.angstrom", + "symbol": "Å", + "tags": ["level","height","distance","length","width","gap","depth","atomic scale","atomic distance","nanoscale", + "angstrom","angstroms","Å"] + }, + { + "name": "unit.nanometer", + "symbol": "nm", + "tags": ["level","height","distance","length","width","gap","depth","nanoscale","atomic scale","molecular scale", + "nanometer","nanometers","nm"] + }, + { + "name": "unit.micrometer", + "symbol": "µm", + "tags": ["level","height","distance","length","width","gap","depth","microns","micrometer","micrometers","µm"] + }, + { + "name": "unit.meter", + "symbol": "m", + "tags": ["level","height","distance","length","width","gap","depth","meter","meters","m"] + }, + { + "name": "unit.kilometer", + "symbol": "km", + "tags": ["distance","height","length","width","gap","depth","kilometer","kilometers","km"] + }, + { + "name": "unit.inch", + "symbol": "in", + "tags": ["level","height","distance","length","width","gap","depth","inch","inches","in"] + }, + { + "name": "unit.foot", + "symbol": "ft", + "tags": ["level","height","distance","length","width","gap","depth","foot","feet","ft"] + }, + { + "name": "unit.yard", + "symbol": "yd", + "tags": ["level","height","distance","length","width","gap","depth","yard","yards","yd"] + }, + { + "name": "unit.mile", + "symbol": "mi", + "tags": ["level","height","distance","length","width","gap","depth","mile","miles","mi"] + }, + { + "name": "unit.nautical-mile", + "symbol": "nm", + "tags": ["level","height","distance","length","width","gap","depth","nautical mile","nm"] + }, + { + "name": "unit.astronomical-unit", + "symbol": "AU", + "tags": ["distance","celestial bodies","solar system","AU"] + }, + { + "name": "unit.reciprocal-metre", + "symbol": "m⁻¹", + "tags": ["wavenumber","wave density","wave frequency","m⁻¹"] + }, + { + "name": "unit.meter-per-meter", + "symbol": "m/m", + "tags": ["ratio of length to length","meter per meter","m/m"] + }, + { + "name": "unit.steradian", + "symbol": "sr", + "tags": ["solid angle","spatial extent","steradian","sr"] + }, + { + "name": "unit.thou", + "symbol": "thou", + "tags": ["length","measurement","thou"] + }, + { + "name": "unit.barleycorn", + "symbol": "barleycorn", + "tags": ["length","shoe size","barleycorn"] + }, + { + "name": "unit.hand", + "symbol": "hand", + "tags": ["length","horse measurement","hand"] + }, + { + "name": "unit.chain", + "symbol": "ch", + "tags": ["length","land surveying","ch"] + }, + { + "name": "unit.furlong", + "symbol": "fur", + "tags": ["length","land surveying","fur"] + }, + { + "name": "unit.league", + "symbol": "league", + "tags": ["length","historical measurement","league"] + }, + { + "name": "unit.fathom", + "symbol": "fathom", + "tags": ["depth","nautical measurement","fathom"] + }, + { + "name": "unit.cable", + "symbol": "cable", + "tags": ["distance","nautical measurement","cable"] + }, + { + "name": "unit.link", + "symbol": "link", + "tags": ["length","land surveying","link"] + }, + { + "name": "unit.rod", + "symbol": "rod", + "tags": ["length","land surveying","rod"] + }, + { + "name": "unit.nanogram", + "symbol": "ng", + "tags": ["mass","weight","heaviness","load","nanogram","nanograms","ng"] + }, + { + "name": "unit.microgram", + "symbol": "μg", + "tags": ["mass","weight","heaviness","load","μg","microgram"] + }, + { + "name": "unit.milligram", + "symbol": "mg", + "tags": ["mass","weight","heaviness","load","milligram","miligrams","mg"] + }, + { + "name": "unit.gram", + "symbol": "g", + "tags": ["mass","weight","heaviness","load","gram","grams","g"] + }, + { + "name": "unit.kilogram", + "symbol": "kg", + "tags": ["mass","weight","heaviness","load","kilogram","kilograms","kg"] + }, + { + "name": "unit.tonne", + "symbol": "t", + "tags": ["mass","weight","heaviness","load","tonne","tons","t"] + }, + { + "name": "unit.ounce", + "symbol": "oz", + "tags": ["mass","weight","heaviness","load","ounce","ounces","oz"] + }, + { + "name": "unit.pound", + "symbol": "lb", + "tags": ["mass","weight","heaviness","load","pound","pounds","lb"] + }, + { + "name": "unit.stone", + "symbol": "st", + "tags": ["mass","weight","heaviness","load","stone","stones","st"] + }, + { + "name": "unit.hundredweight-count", + "symbol": "cwt", + "tags": ["mass","weight","heaviness","load","hundredweight count","cwt"] + }, + { + "name": "unit.short-tons", + "symbol": "short tons", + "tags": ["mass","weight","heaviness","load","short ton","short tons"] + }, + { + "name": "unit.dalton", + "symbol": "Da", + "tags": ["atomic mass unit","AMU","unified atomic mass unit","dalton","Da"] + }, + { + "name": "unit.grain", + "symbol": "gr", + "tags": ["mass","measurement","grain","gr"] + }, + { + "name": "unit.drachm", + "symbol": "dr", + "tags": ["mass","measurement","drachm","dr"] + }, + { + "name": "unit.quarter", + "symbol": "qr", + "tags": ["mass","measurement","quarter","qr"] + }, + { + "name": "unit.slug", + "symbol": "slug", + "tags": ["mass","measurement","slug"] + }, + { + "name": "unit.carat", + "symbol": "ct", + "tags": ["gemstone","pearl","jewelry","carat","ct"] + }, + { + "name": "unit.cubic-millimeter", + "symbol": "mm³", + "tags": ["volume","capacity","extent","cubic millimeter","mm³"] + }, + { + "name": "unit.cubic-centimeter", + "symbol": "cm³", + "tags": ["volume","capacity","extent","cubic centimeter","cubic centimeters","cm³"] + }, + { + "name": "unit.cubic-meter", + "symbol": "m³", + "tags": ["volume","capacity","extent","cubic meter","cubic meters","m³"] + }, + { + "name": "unit.cubic-kilometer", + "symbol": "km³", + "tags": ["volume","capacity","extent","cubic kilometer","cubic kilometers","km³"] + }, + { + "name": "unit.microliter", + "symbol": "µL", + "tags": ["volume","liquid measurement","microliter","µL"] + }, + { + "name": "unit.milliliter", + "symbol": "mL", + "tags": ["volume","capacity","extent","milliliter","milliliters","mL"] + }, + { + "name": "unit.liter", + "symbol": "l", + "tags": ["volume","capacity","extent","liter","liters","l"] + }, + { + "name": "unit.hectoliter", + "symbol": "hl", + "tags": ["volume","capacity","extent","hectoliter","hectoliters","hl"] + }, + { + "name": "unit.cubic-inch", + "symbol": "in³", + "tags": ["volume","capacity","extent","cubic inch","cubic inches","in³"] + }, + { + "name": "unit.cubic-foot", + "symbol": "ft³", + "tags": ["volume","capacity","extent","cubic foot","cubic feet","ft³"] + }, + { + "name": "unit.cubic-yard", + "symbol": "yd³", + "tags": ["volume","capacity","extent","cubic yard","cubic yards","yd³"] + }, + { + "name": "unit.fluid-ounce", + "symbol": "fl-oz", + "tags": ["volume","capacity","extent","fluid ounce","fluid ounces","fl-oz"] + }, + { + "name": "unit.pint", + "symbol": "pt", + "tags": ["volume","capacity","extent","pint","pints","pt"] + }, + { + "name": "unit.quart", + "symbol": "qt", + "tags": ["volume","capacity","extent","quart","quarts","qt"] + }, + { + "name": "unit.gallon", + "symbol": "gal", + "tags": ["volume","capacity","extent","gallon","gallons","gal"] + }, + { + "name": "unit.oil-barrels", + "symbol": "bbl", + "tags": ["volume","capacity","extent","oil barrel","oil barrels","bbl"] + }, + { + "name": "unit.cubic-meter-per-kilogram", + "symbol": "m³/kg", + "tags": ["specific volume","volume per unit mass","cubic meter per kilogram","m³/kg"] + }, + { + "name": "unit.gill", + "symbol": "gi", + "tags": ["volume","liquid measurement","gi"] + }, + { + "name": "unit.hogshead", + "symbol": "hhd", + "tags": ["volume","liquid measurement","hhd"] + }, + { + "name": "unit.teaspoon", + "symbol": "tsp", + "tags": ["volume","cooking measurement","tsp"] + }, + { + "name": "unit.tablespoon", + "symbol": "tbsp", + "tags": ["volume","cooking measurement","tbsp"] + }, + { + "name": "unit.cup", + "symbol": "cup", + "tags": ["volume","cooking measurement","cup"] + }, + { + "name": "unit.celsius", + "symbol": "°C", + "tags": ["temperature","heat","cold","warmth","degrees","celsius","shipment condition","°C"] + }, + { + "name": "unit.kelvin", + "symbol": "K", + "tags": ["temperature","heat","cold","warmth","degrees","kelvin","K","color quality","white balance","color temperature"] + }, + { + "name": "unit.rankine", + "symbol": "°R", + "tags": ["temperature","heat","cold","warmth","Rankine","°R"] + }, + { + "name": "unit.fahrenheit", + "symbol": "°F", + "tags": ["temperature","heat","cold","warmth","degrees","fahrenheit","°F"] + }, + { + "name": "unit.meter-per-second", + "symbol": "m/s", + "tags": ["speed","velocity","pace","meter per second","m/s","peak","peak to peak","root mean square (RMS)", + "vibration","wind speed","weather"] + }, + { + "name": "unit.kilometer-per-hour", + "symbol": "km/h", + "tags": ["speed","velocity","pace","kilometer per hour","km/h"] + }, + { + "name": "unit.foot-per-second", + "symbol": "ft/s", + "tags": ["speed","velocity","pace","foot per second","ft/s"] + }, + { + "name": "unit.mile-per-hour", + "symbol": "mph", + "tags": ["speed","velocity","pace","mile per hour","mph"] + }, + { + "name": "unit.knot", + "symbol": "kt", + "tags": ["speed","velocity","pace","knot","knots","kt"] + }, + { + "name": "unit.millimeters-per-minute", + "symbol": "mm/min", + "tags": ["feed rate","cutting feed rate","millimeters per minute","mm/min"] + }, + { + "name": "unit.kilometer-per-hour-squared", + "symbol": "km/h²", + "tags": ["acceleration","rate of change of velocity","kilometer per hour squared","km/h²"] + }, + { + "name": "unit.foot-per-second-squared", + "symbol": "ft/s²", + "tags": ["acceleration","rate of change of velocity","foot per second squared","ft/s²"] + }, + { + "name": "unit.pascal", + "symbol": "Pa", + "tags": ["pressure","force","compression","tension","pascal","pascals","Pa","atmospheric pressure","air pressure", + "weather","altitude","flight"] + }, + { + "name": "unit.kilopascal", + "symbol": "kPa", + "tags": ["pressure","force","compression","tension","kilopascal","kilopascals","kPa"] + }, + { + "name": "unit.megapascal", + "symbol": "MPa", + "tags": ["pressure","force","compression","tension","megapascal","megapascals","MPa"] + }, + { + "name": "unit.gigapascal", + "symbol": "GPa", + "tags": ["pressure","force","compression","tension","gigapascal","gigapascals","GPa"] + }, + { + "name": "unit.millibar", + "symbol": "mbar", + "tags": ["pressure","force","compression","tension","millibar","millibars","mbar"] + }, + { + "name": "unit.bar", + "symbol": "bar", + "tags": ["pressure","force","compression","tension","bar","bars"] + }, + { + "name": "unit.kilobar", + "symbol": "kbar", + "tags": ["pressure","force","compression","tension","kilobar","kilobars","kbar"] + }, + { + "name": "unit.newton", + "symbol": "N", + "tags": ["force","pressure","newton","newtons","N","push","pull","weight","gravity","N"] + }, + { + "name": "unit.newton-meter", + "symbol": "Nm", + "tags": ["torque","rotational force","newton meter","Nm"] + }, + { + "name": "unit.foot-pounds", + "symbol": "ft·lbf", + "tags": ["torque","rotational force","foot-pound","foot-pounds","ft·lbf"] + }, + { + "name": "unit.inch-pounds", + "symbol": "in·lbf", + "tags": ["torque","rotational force","inch-pounds","inch-pound","in·lbf"] + }, + { + "name": "unit.newton-per-meter", + "symbol": "N/m", + "tags": ["linear density","force per unit length","newton per meter","N/m"] + }, + { + "name": "unit.atmospheres", + "symbol": "atm", + "tags": ["pressure","force","compression","tension","atmosphere","atmospheres","atmospheric pressure","atm"] + }, + { + "name": "unit.pounds-per-square-inch", + "symbol": "psi", + "tags": ["pressure","force","compression","tension","pounds per square inch","psi"] + }, + { + "name": "unit.torr", + "symbol": "Torr", + "tags": ["pressure","force","compression","tension","vacuum pressure","torr"] + }, + { + "name": "unit.inches-of-mercury", + "symbol": "inHg", + "tags": ["pressure","force","compression","tension","vacuum pressure","inHg","atmospheric pressure","barometric pressure"] + }, + { + "name": "unit.pascal-per-square-meter", + "symbol": "Pa/m²", + "tags": ["pressure","stress","mechanical strength","pascal per square meter","Pa/m²"] + }, + { + "name": "unit.pound-per-square-inch", + "symbol": "psi/in²", + "tags": ["pressure","stress","mechanical strength","pound per square inch","psi/in²"] + }, + { + "name": "unit.newton-per-square-meter", + "symbol": "N/m²", + "tags": ["pressure","stress","mechanical strength","newton per square meter","N/m²"] + }, + { + "name": "unit.kilogram-force-per-square-meter", + "symbol": "kgf/m²", + "tags": ["pressure","stress","mechanical strength","kilogram-force per square meter","kgf/m²"] + }, + { + "name": "unit.pascal-per-square-centimeter", + "symbol": "Pa/cm²", + "tags": ["pressure","stress","mechanical strength","pascal per square centimeter","Pa/cm²"] + }, + { + "name": "unit.ton-force-per-square-inch", + "symbol": "tonf/in²", + "tags": ["pressure","stress","mechanical strength","ton-force per square inch","tonf/in²"] + }, + { + "name": "unit.kilonewton-per-square-meter", + "symbol": "kN/m²", + "tags": ["stress","pressure","mechanical strength","kilonewton per square meter","kN/m²"] + }, + { + "name": "unit.newton-per-square-millimeter", + "symbol": "N/mm²", + "tags": ["stress","pressure","mechanical strength","newton per square millimeter","N/mm²"] + }, + { + "name": "unit.microjoule", + "symbol": "μJ", + "tags": ["energy","microjoule","microjoules","μJ"] + }, + { + "name": "unit.millijoule", + "symbol": "mJ", + "tags": ["energy","millijoule","millijoules","mJ"] + }, + { + "name": "unit.joule", + "symbol": "J", + "tags": ["joule","joules","energy","work done","heat","electricity","mechanical work"] + }, + { + "name": "unit.kilojoule", + "symbol": "kJ", + "tags": ["energy","kilojoule","kilojoules","kJ"] + }, + { + "name": "unit.megajoule", + "symbol": "MJ", + "tags": ["energy","megajoule","megajoules","MJ"] + }, + { + "name": "unit.gigajoule", + "symbol": "GJ", + "tags": ["energy","gigajoule","gigajoules","GJ"] + }, + { + "name": "unit.watt-hour", + "symbol": "Wh", + "tags": ["energy","watt-hour","watt-hours","energy usage","power consumption","energy consumption","electricity usage"] + }, + { + "name": "unit.kilowatt-hour", + "symbol": "kWh", + "tags": ["energy","kilowatt-hour","kilowatt-hours","energy usage","power consumption","energy consumption","electricity usage"] + }, + { + "name": "unit.electron-volts", + "symbol": "eV", + "tags": ["energy","subatomic particles","radiation"] + }, + { + "name": "unit.joules-per-coulomb", + "symbol": "J/C", + "tags": ["electrical potential energy","voltage","joules per coulomb","J/C"] + }, + { + "name": "unit.british-thermal-unit", + "symbol": "BTU", + "tags": ["energy","heat","work done","british thermal unit","british thermal units","BTU"] + }, + { + "name": "unit.foot-pound", + "symbol": "ft·lb", + "tags": ["energy","foot-pound","foot-pounds","ft·lb","ft⋅lbf"] + }, + { + "name": "unit.calorie", + "symbol": "Cal", + "tags": ["energy","food energy","Calorie","Calories","Cal"] + }, + { + "name": "unit.small-calorie", + "symbol": "cal", + "tags": ["energy","small calorie","calories","cal"] + }, + { + "name": "unit.kilocalorie", + "symbol": "kcal", + "tags": ["energy","small calorie","kilocalories","kcal"] + }, + { + "name": "unit.joule-per-kelvin", + "symbol": "J/K", + "tags": ["specific heat capacity","heat capacity per unit temperature","joule per kelvin","J/K"] + }, + { + "name": "unit.joule-per-kilogram-kelvin", + "symbol": "J/(kg·K)", + "tags": ["specific heat capacity","heat capacity per unit mass and temperature","joule per kilogram-kelvin","J/(kg·K)"] + }, + { + "name": "unit.joule-per-kilogram", + "symbol": "J/kg", + "tags": ["specific energy","specific energy capacity","joule per kilogram","J/kg"] + }, + { + "name": "unit.watt-per-meter-kelvin", + "symbol": "W/(m·K)", + "tags": ["thermal conductivity","watt per meter-kelvin","W/(m·K)"] + }, + { + "name": "unit.joule-per-cubic-meter", + "symbol": "J/m³", + "tags": ["energy density","joule per cubic meter","J/m³"] + }, + { + "name": "unit.therm", + "symbol": "thm", + "tags": ["energy","natural gas consumption","BTU","therm","thm"] + }, + { + "name": "unit.electric-dipole-moment", + "symbol": "C·m", + "tags": ["electric dipole","dipole moment","coulomb meter","C·m"] + }, + { + "name": "unit.magnetic-dipole-moment", + "symbol": "A·m²", + "tags": ["magnetic dipole","dipole moment","ampere square meter","A·m²"] + }, + { + "name": "unit.debye", + "symbol": "D", + "tags": ["polarization","electric dipole moment","debye","D"] + }, + { + "name": "unit.coulomb-per-square-meter-per-volt", + "symbol": "C·m²/V", + "tags": ["polarization","electric field","coulomb per square meter per volt","C·m²/V"] + }, + { + "name": "unit.milliwatt", + "symbol": "mW", + "tags": ["power","horsepower","performance","milliwatt","milliwatts","electricity","mW"] + }, + { + "name": "unit.microwatt", + "symbol": "μW", + "tags": ["power","horsepower","performance","microwatt","microwatts","electricity","μW"] + }, + { + "name": "unit.watt", + "symbol": "W", + "tags": ["power","horsepower","performance","watt","watts","electricity","W"] + }, + { + "name": "unit.kilowatt", + "symbol": "kW", + "tags": ["power","horsepower","performance","kilowatt","kilowatts","electricity","kW"] + }, + { + "name": "unit.megawatt", + "symbol": "MW", + "tags": ["power","horsepower","performance","megawatt","megawatts","electricity","MW"] + }, + { + "name": "unit.gigawatt", + "symbol": "GW", + "tags": ["power","horsepower","performance","gigawatt","gigawatts","electricity","GW"] + }, + { + "name": "unit.metric-horsepower", + "symbol": "PS", + "tags": ["power","performance","metric horsepower","PS"] + }, + { + "name": "unit.milliwatt-per-square-centimeter", + "symbol": "mW/cm²", + "tags": ["power density","radiation intensity","sunlight intensity","signal power","intensity", + "milliwatts per square centimeter","UV Intensity","mW/cm²"] + }, + { + "name": "unit.watt-per-square-centimeter", + "symbol": "W/cm²", + "tags": ["power density","intensity of power","watts per square centimeter","W/cm²"] + }, + { + "name": "unit.kilowatt-per-square-centimeter", + "symbol": "kW/cm²", + "tags": ["power density","intensity of power","kilowatts per square centimeter","kW/cm²"] + }, + { + "name": "unit.milliwatt-per-square-meter", + "symbol": "mW/m²", + "tags": ["power density","intensity of power","milliwatts per square meter","mW/m²"] + }, + { + "name": "unit.watt-per-square-meter", + "symbol": "W/m²", + "tags": ["power density","intensity of power","watts per square meter","W/m²"] + }, + { + "name": "unit.kilowatt-per-square-meter", + "symbol": "kW/m²", + "tags": ["power density","intensity of power","kilowatts per square meter","kW/m²"] + }, + { + "name": "unit.watt-per-square-inch", + "symbol": "W/in²", + "tags": ["power density","intensity of power","watts per square inch","W/in²"] + }, + { + "name": "unit.kilowatt-per-square-inch", + "symbol": "kW/in²", + "tags": ["power density","intensity of power","kilowatts per square inch","kW/in²"] + }, + { + "name": "unit.horsepower", + "symbol": "hp", + "tags": ["power","horsepower","performance","electricity","horsepowers","hp"] + }, + { + "name": "unit.btu-per-hour", + "symbol": "BTU/h", + "tags": ["power","heat transfer","thermal energy","HVAC","BTU/h"] + }, + { + "name": "unit.coulomb", + "symbol": "C", + "tags": ["charge","electricity","electrostatics","Coulomb","C"] + }, + { + "name": "unit.millicoulomb", + "symbol": "mC", + "tags": ["charge","electricity","electrostatics","millicoulombs","mC"] + }, + { + "name": "unit.microcoulomb", + "symbol": "µC", + "tags": ["charge","electricity","electrostatics","microcoulomb","µC"] + }, + { + "name": "unit.picocoulomb", + "symbol": "pC", + "tags": ["charge","electricity","electrostatics","picocoulomb","pC"] + }, + { + "name": "unit.coulomb-per-meter", + "symbol": "C/m", + "tags": ["electric displacement field per length","coulomb per meter","C/m"] + }, + { + "name": "unit.coulomb-per-cubic-meter", + "symbol": "C/m³", + "tags": ["electric charge density","coulomb per cubic meter","C/m³"] + }, + { + "name": "unit.coulomb-per-square-meter", + "symbol": "C/m²", + "tags": ["electric surface charge density","coulomb per square meter","C/m²"] + }, + { + "name": "unit.square-millimeter", + "symbol": "mm²", + "tags": ["area","lot","zone","space","region","square millimeter","square millimeters","mm²","sq-mm"] + }, + { + "name": "unit.square-centimeter", + "symbol": "cm²", + "tags": ["area","lot","zone","space","region","square centimeter","square centimeters","cm²","sq-cm"] + }, + { + "name": "unit.square-meter", + "symbol": "m²", + "tags": ["area","lot","zone","space","region","square meter","square meters","m²","sq-m"] + }, + { + "name": "unit.hectare", + "symbol": "ha", + "tags": ["area","lot","zone","space","region","hectare","hectares","ha"] + }, + { + "name": "unit.square-kilometer", + "symbol": "km²", + "tags": ["area","lot","zone","space","region","square kilometer","square kilometers","km²","sq-km"] + }, + { + "name": "unit.square-inch", + "symbol": "in²", + "tags": ["area","lot","zone","space","region","square inch","square inches","in²","sq-in"] + }, + { + "name": "unit.square-foot", + "symbol": "ft²", + "tags": ["area","lot","zone","space","region","square foot","square feet","ft²","sq-ft"] + }, + { + "name": "unit.square-yard", + "symbol": "yd²", + "tags": ["area","lot","zone","space","region","square yard","square yards","yd²","sq-yd"] + }, + { + "name": "unit.acre", + "symbol": "a", + "tags": ["area","lot","zone","space","region","acre","acres","a"] + }, + { + "name": "unit.square-mile", + "symbol": "ml²", + "tags": ["area","lot","zone","space","region","square mile","square miles","ml²","sq-mi"] + }, + { + "name": "unit.are", + "symbol": "are", + "tags": ["area","land measurement","are"] + }, + { + "name": "unit.barn", + "symbol": "barn", + "tags": ["cross-sectional area","particle physics","nuclear physics","barn"] + }, + { + "name": "unit.circular-inch", + "symbol": "circin", + "tags": ["area","circular measurement","circular inch","circin"] + }, + { + "name": "unit.milliampere-hour", + "symbol": "mAh", + "tags": ["electric current","current flow","electric charge","current capacity","flow of electricity", + "electrical flow","milliampere-hour","milliampere-hours","mAh"] + }, + { + "name": "unit.ampere-hours", + "symbol": "Ah", + "tags": ["electric current","current flow","electric charge","current capacity","flow of electricity", + "electrical flow","ampere","ampere-hours","Ah"] + }, + { + "name": "unit.kiloampere-hours", + "symbol": "kAh", + "tags": ["electric current","current flow","electric charge","current capacity","flow of electricity","electrical flow", + "kiloampere-hours","kiloampere-hour","kAh"] + }, + { + "name": "unit.nanoampere", + "symbol": "nA", + "tags": ["current","amperes","nanoampere","nA"] + }, + { + "name": "unit.picoampere", + "symbol": "pA", + "tags": ["current","amperes","picoampere","pA"] + }, + { + "name": "unit.microampere", + "symbol": "μA", + "tags": ["electric current","microampere","microamperes","μA"] + }, + { + "name": "unit.milliampere", + "symbol": "mA", + "tags": ["electric current","milliampere","milliamperes","mA"] + }, + { + "name": "unit.ampere", + "symbol": "A", + "tags": ["electric current","current flow","flow of electricity","electrical flow","ampere","amperes","amperage","A"] + }, + { + "name": "unit.kiloamperes", + "symbol": "kA", + "tags": ["electric current","current flow","kiloamperes","kA"] + }, + { + "name": "unit.microampere-per-square-centimeter", + "symbol": "µA/cm²", + "tags": ["Current density","microampere per square centimeter","µA/cm²"] + }, + { + "name": "unit.ampere-per-square-meter", + "symbol": "A/m²", + "tags": ["current density","current per unit area","ampere per square meter","A/m²"] + }, + { + "name": "unit.ampere-per-meter", + "symbol": "A/m", + "tags": ["magnetic field strength","magnetic field intensity","ampere per meter","A/m"] + }, + { + "name": "unit.oersted", + "symbol": "Oe", + "tags": ["magnetic field","oersted","Oe"] + }, + { + "name": "unit.bohr-magneton", + "symbol": "μB", + "tags": ["atomic physics","magnetic moment","bohr magneton","μB"] + }, + { + "name": "unit.ampere-meter-squared", + "symbol": "A·m²", + "tags": ["magnetic moment","dipole moment","ampere-meter squared","A·m²"] + }, + { + "name": "unit.ampere-meter", + "symbol": "A·m", + "tags": ["magnetic field","current loop","ampere-meter","A·m"] + }, + { + "name": "unit.nanovolt", + "symbol": "nV", + "tags": ["voltage","volts","nanovolt","nV"] + }, + { + "name": "unit.picovolt", + "symbol": "pV", + "tags": ["voltage","volts","picovolt","pV"] + }, + { + "name": "unit.millivolts", + "symbol": "mV", + "tags": ["electric potential","electric tension","voltage","millivolt","millivolts","mV"] + }, + { + "name": "unit.microvolts", + "symbol": "μV", + "tags": ["electric potential","electric tension","voltage","microvolt","microvolts","μV"] + }, + { + "name": "unit.volt", + "symbol": "V", + "tags": ["electric potential","electric tension","voltage","volt","volts","V","power source","battery","battery level"] + }, + { + "name": "unit.kilovolts", + "symbol": "kV", + "tags": ["electric potential","electric tension","voltage","kilovolt","kilovolts","kV"] + }, + { + "name": "unit.dbmV", + "symbol": "dBmV", + "tags": ["decibels millivolt","voltage level","signal","dBmV"] + }, + { + "name": "unit.volt-meter", + "symbol": "V·m", + "tags": ["electric flux","volt-meter","V·m"] + }, + { + "name": "unit.kilovolt-meter", + "symbol": "kV·m", + "tags": ["electric flux","kilovolt-meter","kV·m"] + }, + { + "name": "unit.megavolt-meter", + "symbol": "MV·m", + "tags": ["electric flux","megavolt-meter","MV·m"] + }, + { + "name": "unit.microvolt-meter", + "symbol": "µV·m", + "tags": ["electric flux","microvolt-meter","µV·m"] + }, + { + "name": "unit.millivolt-meter", + "symbol": "mV·m", + "tags": ["electric flux","millivolt-meter","mV·m"] + }, + { + "name": "unit.nanovolt-meter", + "symbol": "nV·m", + "tags": ["electric flux","nanovolt-meter","nV·m"] + }, + { + "name": "unit.ohm", + "symbol": "Ω", + "tags": ["electrical resistance","resistance","impedance","ohm"] + }, + { + "name": "unit.microohm", + "symbol": "μΩ", + "tags": ["electrical resistance","resistance","microohm","μΩ"] + }, + { + "name": "unit.milliohm", + "symbol": "mΩ", + "tags": ["electrical resistance","resistance","milliohm","mΩ"] + }, + { + "name": "unit.kilohm", + "symbol": "kΩ", + "tags": ["electrical resistance","resistance","kilohm","kΩ"] + }, + { + "name": "unit.megohm", + "symbol": "MΩ", + "tags": ["electrical resistance","resistance","megohm","MΩ"] + }, + { + "name": "unit.gigohm", + "symbol": "GΩ", + "tags": ["electrical resistance","resistance","gigohm","GΩ"] + }, + { + "name": "unit.hertz", + "symbol": "Hz", + "tags": ["frequency","cycles per second","hertz","Hz"] + }, + { + "name": "unit.kilohertz", + "symbol": "kHz", + "tags": ["frequency","cycles per second","kilohertz","kHz"] + }, + { + "name": "unit.megahertz", + "symbol": "MHz", + "tags": ["frequency","cycles per second","megahertz","MHz"] + }, + { + "name": "unit.gigahertz", + "symbol": "GHz", + "tags": ["frequency","cycles per second","gigahertz","GHz"] + }, + { + "name": "unit.rpm", + "symbol": "RPM", + "tags": ["speed","velocity","cycle","engine","Revolutions Per Minute","RPM","angular velocity","rotation speed"] + }, + { + "name": "unit.candela-per-square-meter", + "symbol": "cd/m²", + "tags": ["brightness","light level","Luminance","Candela per square meter","cd/m²"] + }, + { + "name": "unit.candela", + "symbol": "cd", + "tags": ["light intensity","candle power","luminous intensity","Candela","cd"] + }, + { + "name": "unit.lumen", + "symbol": "lm", + "tags": ["total light output","light power","luminous flux","Lumen","lm"] + }, + { + "name": "unit.lux", + "symbol": "lx", + "tags": ["illumination","light level on a surface","illuminance","Lux","lx"] + }, + { + "name": "unit.foot-candle", + "symbol": "fc", + "tags": ["illuminance","light level","foot-candle","fc"] + }, + { + "name": "unit.lumen-per-square-meter", + "symbol": "lm/m²", + "tags": ["illuminance","light level","lumen per square meter","lm/m²"] + }, + { + "name": "unit.lux-second", + "symbol": "lx·s", + "tags": ["light exposure","illumination time","light dosage","Lux second","lx·s"] + }, + { + "name": "unit.lumen-second", + "symbol": "lm·s", + "tags": ["total light energy","luminous energy","Lumen second","lm·s"] + }, + { + "name": "unit.lumens-per-watt", + "symbol": "lm/W", + "tags": ["lighting efficiency","light output per energy","luminous efficacy","Lumens per watt","lm/W"] + }, + { + "name": "unit.absorbance", + "symbol": "AU", + "tags": ["optical density","light absorption","absorbance","AU"] + }, + { + "name": "unit.mole", + "symbol": "mol", + "tags": ["amount of substance","substance quantity","mole","moles","mol"] + }, + { + "name": "unit.nanomole", + "symbol": "nmol", + "tags": ["amount of substance","substance quantity","concentration","nanomole","nmol"] + }, + { + "name": "unit.micromole", + "symbol": "μmol", + "tags": ["amount of substance","substance quantity","micromole","μmol"] + }, + { + "name": "unit.millimole", + "symbol": "mmol", + "tags": ["amount of substance","substance quantity","millimole","mmol"] + }, + { + "name": "unit.kilomole", + "symbol": "kmol", + "tags": ["amount of substance","substance quantity","kilomole","kmol"] + }, + { + "name": "unit.mole-per-cubic-meter", + "symbol": "mol/m³", + "tags": ["concentration","amount of substance","mole per cubic meter","mol/m³"] + }, + { + "name": "unit.battery", + "symbol": "%", + "tags": ["power source","state of charge (SoC)","battery","battery level","level","humidity","moisture", + "relative humidity","water content","soil moisture","irrigation","water in soil","soil water content","VWC", + "Volumetric Water Content","Total Harmonic Distortion","THD","power quality","UV Transmittance","%"] + }, + { + "name": "unit.rssi", + "symbol": "rssi", + "tags": ["signal strength","signal level","received signal strength indicator","rssi","dBm"] + }, + { + "name": "unit.ppm", + "symbol": "ppm", + "tags": ["carbon dioxide","co²","carbon monoxide","co","aqi","air quality","total volatile organic compounds","tvoc","ppm"] + }, + { + "name": "unit.ppb", + "symbol": "ppb", + "tags": ["ozone","o³","nitrogen dioxide","no²","sulfur dioxide","so²","aqi","air quality","tvoc","ppb"] + }, + { + "name": "unit.micrograms-per-cubic-meter", + "symbol": "µg/m³", + "tags": ["coarse particulate matter","pm10","fine particulate matter","pm2.5","aqi","air quality", + "total volatile organic compounds","tvoc","micrograms per cubic meter","µg/m³"] + }, + { + "name": "unit.aqi", + "symbol": "aqi", + "tags": ["AQI","air quality index"] + }, + { + "name": "unit.gram-per-cubic-meter", + "symbol": "g/m³", + "tags": ["humidity","moisture","absolute humidity","g/m³"] + }, + { + "name": "unit.gram-per-kilogram", + "symbol": "g/kg", + "tags": ["humidity","moisture","specific humidity","g/kg"] + }, + { + "name": "unit.millimeters-per-second", + "symbol": "mm/s", + "tags": ["velocity","speed","rate of motion","peak","peak to peak","root mean square (RMS)","vibration","mm/s"] + }, + { + "name": "unit.neper", + "symbol": "Np", + "tags": ["logarithmic unit","ratio","gain","loss","attenuation","neper","Np"] + }, + { + "name": "unit.bel", + "symbol": "B", + "tags": ["logarithmic unit","power ratio","intensity ratio","bel","B"] + }, + { + "name": "unit.decibel", + "symbol": "dB", + "tags": ["noise level","sound level","volume","acoustics","decibel","dB"] + }, + { + "name": "unit.meters-per-second-squared", + "symbol": "m/s²", + "tags": ["peak","peak to peak","root mean square (RMS)","vibration","meters per second squared","m/s²"] + }, + { + "name": "unit.becquerel", + "symbol": "Bq", + "tags": ["radioactivity","radiation","becquerel","Bq"] + }, + { + "name": "unit.curie", + "symbol": "Ci", + "tags": ["radioactivity","radiation","curie","Ci"] + }, + { + "name": "unit.gray", + "symbol": "Gy", + "tags": ["radiation dose","gray","Gy"] + }, + { + "name": "unit.sievert", + "symbol": "Sv", + "tags": ["radiation dose","sievert","radiation dose equivalent2","Sv"] + }, + { + "name": "unit.roentgen", + "symbol": "R", + "tags": ["radiation exposure","roentgen","R"] + }, + { + "name": "unit.cps", + "symbol": "cps", + "tags": ["radiation detection","counts per second","cps"] + }, + { + "name": "unit.rad", + "symbol": "Rad", + "tags": ["radiation dose","rad"] + }, + { + "name": "unit.rem", + "symbol": "Rem", + "tags": ["radiation dose equivalent","rem"] + }, + { + "name": "unit.dps", + "symbol": "dps", + "tags": ["radioactive decay","radioactivity","disintegrations per second","dps"] + }, + { + "name": "unit.rutherford", + "symbol": "Rd", + "tags": ["radioactive decay","radioactivity","rutherford","Rd"] + }, + { + "name": "unit.coulombs-per-kilogram", + "symbol": "C/kg", + "tags": ["radiation exposure","dose","coulombs per kilogram","electric charge-to-mass ratio","C/kg"] + }, + { + "name": "unit.becquerels-per-cubic-meter", + "symbol": "Bq/m³", + "tags": ["radioactivity","radiation","becquerels per cubic meter","Bq/m³"] + }, + { + "name": "unit.curies-per-liter", + "symbol": "Ci/L", + "tags": ["radioactivity","radiation","curies per liter","Ci/L"] + }, + { + "name": "unit.becquerels-per-second", + "symbol": "Bq/s", + "tags": ["radioactive decay rate","becquerels per second","Bq/s"] + }, + { + "name": "unit.curies-per-second", + "symbol": "Ci/s", + "tags": ["radioactive decay rate","curies per second","Ci/s"] + }, + { + "name": "unit.gy-per-second", + "symbol": "Gy/s", + "tags": ["absorbed dose rate","radiation dose rate","gray per second","Gy/s"] + }, + { + "name": "unit.watt-per-steradian", + "symbol": "W/sr", + "tags": ["radiant intensity","power per unit solid angle","watt per steradian","W/sr"] + }, + { + "name": "unit.watt-per-square-metre-steradian", + "symbol": "W/(m²·sr)", + "tags": ["radiance","radiant flux density","watt per square metre-steradian","W/(m²·sr)"] + }, + { + "name": "unit.ph-level", + "symbol": "pH", + "tags": ["acidity","alkalinity","neutral","acid","base","pH","soil pH","water quality","water pH"] + }, + { + "name": "unit.turbidity", + "symbol": "NTU", + "tags": ["water turbidity","water clarity","Nephelometric Turbidity Units","NTU"] + }, + { + "name": "unit.mg-per-liter", + "symbol": "mg/L", + "tags": ["dissolved oxygen","water quality","mg/L"] + }, + { + "name": "unit.microsiemens-per-centimeter", + "symbol": "µS/cm", + "tags": ["Electrical conductivity","water quality","soil quality","microsiemens per centimeter","µS/cm"] + }, + { + "name": "unit.millisiemens-per-meter", + "symbol": "mS/m", + "tags": ["Electrical conductivity","water quality","soil quality","millisiemens per meter","mS/m"] + }, + { + "name": "unit.siemens-per-meter", + "symbol": "S/m", + "tags": ["Electrical conductivity","water quality","soil quality","siemens per meter","S/m"] + }, + { + "name": "unit.kilogram-per-cubic-meter", + "symbol": "kg/m³", + "tags": ["density","mass per unit volume","kg/m³"] + }, + { + "name": "unit.gram-per-cubic-centimeter", + "symbol": "g/cm³", + "tags": ["density","mass per unit volume","g/cm³"] + }, + { + "name": "unit.kilogram-per-square-meter", + "symbol": "kg/m²", + "tags": ["density","surface density","areal density","mass per unit area","kg/m²"] + }, + { + "name": "unit.milligram-per-milliliter", + "symbol": "mg/mL", + "tags": ["concentration","mass per volume","mg/mL"] + }, + { + "name": "unit.pound-per-cubic-foot", + "symbol": "lb/ft³", + "tags": ["Density","mass per unit volume","lb/ft³"] + }, + { + "name": "unit.ounces-per-cubic-inch", + "symbol": "oz/in³", + "tags": ["density","mass per unit volume","oz/in³"] + }, + { + "name": "unit.tons-per-cubic-yard", + "symbol": "ton/yd³", + "tags": ["density","mass per unit volume","ton/yd³"] + }, + { + "name": "unit.particle-density", + "symbol": "particles/mL", + "tags": ["particle concentration","count","particles/mL"] + }, + { + "name": "unit.kilometers-per-liter", + "symbol": "km/L", + "tags": ["fuel efficiency","km/L"] + }, + { + "name": "unit.miles-per-gallon", + "symbol": "mpg", + "tags": ["fuel efficiency","mpg"] + }, + { + "name": "unit.liters-per-100-km", + "symbol": "L/100km", + "tags": ["fuel efficiency","L/100km"] + }, + { + "name": "unit.gallons-per-mile", + "symbol": "gal/mi", + "tags": ["fuel efficiency","gal/mi"] + }, + { + "name": "unit.liters-per-hour", + "symbol": "L/hr", + "tags": ["fuel consumption","L/hr"] + }, + { + "name": "unit.gallons-per-hour", + "symbol": "gal/hr", + "tags": ["fuel consumption","gal/hr"] + }, + { + "name": "unit.beats-per-minute", + "symbol": "bpm", + "tags": ["heart rate","pulse","bpm"] + }, + { + "name": "unit.millimeters-of-mercury", + "symbol": "mmHg", + "tags": ["blood pressure","systolic","diastolic","mmHg"] + }, + { + "name": "unit.milligrams-per-deciliter", + "symbol": "mg/dL", + "tags": ["glucose","blood sugar","glucose level","mg/dL"] + }, + { + "name": "unit.g-force", + "symbol": "G", + "tags": ["acceleration","gravity","force","g-load","G"] + }, + { + "name": "unit.kilonewton", + "symbol": "kN", + "tags": ["force","kN"] + }, + { + "name": "unit.kilogram-force", + "symbol": "kgf", + "tags": ["force","kgf"] + }, + { + "name": "unit.pound-force", + "symbol": "lbf", + "tags": ["force","lbf"] + }, + { + "name": "unit.kilopound-force", + "symbol": "klbf", + "tags": ["force","klbf"] + }, + { + "name": "unit.dyne", + "symbol": "dyn", + "tags": ["force","dyn"] + }, + { + "name": "unit.poundal", + "symbol": "pdl", + "tags": ["force","pdl"] + }, + { + "name": "unit.kip", + "symbol": "kip", + "tags": ["force","kip"] + }, + { + "name": "unit.gal", + "symbol": "Gal", + "tags": ["acceleration","gravity","g-force","Gal"] + }, + { + "name": "unit.gravity", + "symbol": "gravity", + "tags": ["acceleration","gravity","g-force"] + }, + { + "name": "unit.hectopascal", + "symbol": "hPa", + "tags": ["atmospheric pressure","air pressure","weather","altitude","flight","hPa"] + }, + { + "name": "unit.atmosphere", + "symbol": "atm", + "tags": ["atmospheric pressure","air pressure","weather","altitude","flight","atm"] + }, + { + "name": "unit.millibars", + "symbol": "mb", + "tags": ["atmospheric pressure","air pressure","weather","altitude","flight","mb"] + }, + { + "name": "unit.inch-of-mercury", + "symbol": "inHg", + "tags": ["atmospheric pressure","air pressure","weather","altitude","flight","inHg","richter"] + }, + { + "name": "unit.richter-scale", + "symbol": "richter", + "tags": ["earthquake","seismic activity","richter"] + }, + { + "name": "unit.percentage", + "symbol": "%", + "tags": ["percentage"] + }, + { + "name": "unit.second", + "symbol": "s", + "tags": ["time","duration","interval","angle","second","arcsecond","sec"] + }, + { + "name": "unit.minute", + "symbol": "min", + "tags": ["time","duration","interval","angle","minute","arcminute","min"] + }, + { + "name": "unit.hour", + "symbol": "h", + "tags": ["time","duration","interval","h"] + }, + { + "name": "unit.day", + "symbol": "d", + "tags": ["time","duration","interval","d"] + }, + { + "name": "unit.week", + "symbol": "wk", + "tags": ["time","duration","interval","wk"] + }, + { + "name": "unit.month", + "symbol": "mo", + "tags": ["time","duration","interval","mo"] + }, + { + "name": "unit.year", + "symbol": "yr", + "tags": ["time","duration","interval","yr"] + }, + { + "name": "unit.cubic-foot-per-minute", + "symbol": "ft³/min", + "tags": ["airflow","ventilation","HVAC","gas flow rate","CFM","flow rate","fluid flow","cubic foot per minute","ft³/min"] + }, + { + "name": "unit.cubic-meters-per-hour", + "symbol": "m³/hr", + "tags": ["airflow","ventilation","HVAC","gas flow rate","cubic meters per hour","m³/hr"] + }, + { + "name": "unit.cubic-meters-per-second", + "symbol": "m³/s", + "tags": ["airflow","ventilation","HVAC","gas flow rate","cubic meters per second","m³/s"] + }, + { + "name": "unit.liter-per-second", + "symbol": "L/s", + "tags": ["airflow","ventilation","HVAC","gas flow rate","liter per second","L/s"] + }, + { + "name": "unit.liter-per-minute", + "symbol": "L/min", + "tags": ["airflow","ventilation","HVAC","gas flow rate","liter per minute","L/min"] + }, + { + "name": "unit.gallons-per-minute", + "symbol": "GPM", + "tags": ["airflow","ventilation","HVAC","gas flow rate","gallons per minute","GPM"] + }, + { + "name": "unit.cubic-foot-per-second", + "symbol": "ft³/s", + "tags": ["flow rate","fluid flow","cubic foot per second","cubic feet per second","ft³/s"] + }, + { + "name": "unit.milliliters-per-minute", + "symbol": "mL/min", + "tags": ["Flow rate","fluid dynamics","milliliters per minute","mL/min"] + }, + { + "name": "unit.bit", + "symbol": "bit", + "tags": ["data","binary digit","information","bit"] + }, + { + "name": "unit.byte", + "symbol": "B", + "tags": ["data","byte","information","storage","memory","B"] + }, + { + "name": "unit.kilobyte", + "symbol": "KB", + "tags": ["data","kilobyte","KB"] + }, + { + "name": "unit.megabyte", + "symbol": "MB", + "tags": ["data","megabyte","MB"] + }, + { + "name": "unit.gigabyte", + "symbol": "GB", + "tags": ["data","gigabyte","GB"] + }, + { + "name": "unit.terabyte", + "symbol": "TB", + "tags": ["data","terabyte","TB"] + }, + { + "name": "unit.petabyte", + "symbol": "PB", + "tags": ["data","petabyte","PB"] + }, + { + "name": "unit.exabyte", + "symbol": "EB", + "tags": ["data","exabyte","EB"] + }, + { + "name": "unit.zettabyte", + "symbol": "ZB", + "tags": ["data","zettabyte","ZB"] + }, + { + "name": "unit.yottabyte", + "symbol": "YB", + "tags": ["data","yottabyte","YB"] + }, + { + "name": "unit.bit-per-second", + "symbol": "bps", + "tags": ["data transfer rate","bps"] + }, + { + "name": "unit.kilobit-per-second", + "symbol": "kbps", + "tags": ["data transfer rate","kbps"] + }, + { + "name": "unit.megabit-per-second", + "symbol": "Mbps", + "tags": ["data transfer rate","Mbps"] + }, + { + "name": "unit.gigabit-per-second", + "symbol": "Gbps", + "tags": ["data transfer rate","Gbps"] + }, + { + "name": "unit.terabit-per-second", + "symbol": "Tbps", + "tags": ["data transfer rate","Tbps"] + }, + { + "name": "unit.byte-per-second", + "symbol": "B/s", + "tags": ["data transfer rate","B/s"] + }, + { + "name": "unit.kilobyte-per-second", + "symbol": "KB/s", + "tags": ["data transfer rate","KB/s"] + }, + { + "name": "unit.megabyte-per-second", + "symbol": "MB/s", + "tags": ["data transfer rate","MB/s"] + }, + { + "name": "unit.gigabyte-per-second", + "symbol": "GB/s", + "tags": ["data transfer rate","GB/s"] + }, + { + "name": "unit.degree", + "symbol": "deg", + "tags": ["angle","degree","degrees","deg"] + }, + { + "name": "unit.radian", + "symbol": "rad", + "tags": ["angle","radian","radians","rad"] + }, + { + "name": "unit.gradian", + "symbol": "grad", + "tags": ["angle","gradian","grades","grad"] + }, + { + "name": "unit.mil", + "symbol": "mil", + "tags": ["angle","military angle","angular mil","mil"] + }, + { + "name": "unit.revolution", + "symbol": "rev", + "tags": ["angle","revolution","full circle","complete turn","rev"] + }, + { + "name": "unit.siemens", + "symbol": "S", + "tags": ["electrical conductance","conductance","siemens","S"] + }, + { + "name": "unit.millisiemens", + "symbol": "mS", + "tags": ["electrical conductance","conductance","millisiemens","mS"] + }, + { + "name": "unit.microsiemens", + "symbol": "μS", + "tags": ["electrical conductance","conductance","microsiemens","μS"] + }, + { + "name": "unit.kilosiemens", + "symbol": "kS", + "tags": ["electrical conductance","conductance","kilosiemens","kS"] + }, + { + "name": "unit.megasiemens", + "symbol": "MS", + "tags": ["electrical conductance","conductance","megasiemens","MS"] + }, + { + "name": "unit.gigasiemens", + "symbol": "GS", + "tags": ["electrical conductance","conductance","gigasiemens","GS"] + }, + { + "name": "unit.farad", + "symbol": "F", + "tags": ["electric capacitance","capacitance","farad","F"] + }, + { + "name": "unit.millifarad", + "symbol": "mF", + "tags": ["electric capacitance","capacitance","millifarad","mF"] + }, + { + "name": "unit.microfarad", + "symbol": "μF", + "tags": ["electric capacitance","capacitance","microfarad","μF"] + }, + { + "name": "unit.nanofarad", + "symbol": "nF", + "tags": ["electric capacitance","capacitance","nanofarad","nF"] + }, + { + "name": "unit.picofarad", + "symbol": "pF", + "tags": ["electric capacitance","capacitance","picofarad","pF"] + }, + { + "name": "unit.kilofarad", + "symbol": "kF", + "tags": ["electric capacitance","capacitance","kilofarad","kF"] + }, + { + "name": "unit.megafarad", + "symbol": "MF", + "tags": ["electric capacitance","capacitance","megafarad","MF"] + }, + { + "name": "unit.gigafarad", + "symbol": "GF", + "tags": ["electric capacitance","capacitance","gigafarad","GF"] + }, + { + "name": "unit.terfarad", + "symbol": "TF", + "tags": ["electric capacitance","capacitance","terafarad","TF"] + }, + { + "name": "unit.farad-per-meter", + "symbol": "F/m", + "tags": ["electric permittivity","farad per meter","F/m"] + }, + { + "name": "unit.tesla", + "symbol": "T", + "tags": ["magnetic field","magnetic field strength","tesla","T","magnetic flux density"] + }, + { + "name": "unit.gauss", + "symbol": "G", + "tags": ["magnetic field","magnetic field strength","gauss","G","magnetic flux density"] + }, + { + "name": "unit.kilogauss", + "symbol": "kG", + "tags": ["magnetic field","magnetic field strength","kilogauss","kG","magnetic flux density"] + }, + { + "name": "unit.millitesla", + "symbol": "mT", + "tags": ["magnetic field","magnetic field strength","millitesla","mT"] + }, + { + "name": "unit.microtesla", + "symbol": "μT", + "tags": ["magnetic field","magnetic field strength","microtesla","μT"] + }, + { + "name": "unit.nanotesla", + "symbol": "nT", + "tags": ["magnetic field","magnetic field strength","nanotesla","nT"] + }, + { + "name": "unit.kilotesla", + "symbol": "kT", + "tags": ["magnetic field","magnetic field strength","kilotesla","kT"] + }, + { + "name": "unit.megatesla", + "symbol": "MT", + "tags": ["magnetic field","magnetic field strength","megatesla","MT"] + }, + { + "name": "unit.millitesla-square-meters", + "symbol": "millitesla square meters", + "tags": ["magnetic field","millitesla square meters"] + }, + { + "name": "unit.gamma", + "symbol": "γ", + "tags": ["magnetic flux density","gamma","γ"] + }, + { + "name": "unit.lambda", + "symbol": "λ", + "tags": ["wavelength","lambda","λ"] + }, + { + "name": "unit.square-meter-per-second", + "symbol": "m²/s", + "tags": ["kinematic viscosity","m²/s"] + }, + { + "name": "unit.square-centimeter-per-second", + "symbol": "cm²/s", + "tags": ["kinematic viscosity","cm²/s"] + }, + { + "name": "unit.stoke", + "symbol": "St", + "tags": ["kinematic viscosity","stokes","St"] + }, + { + "name": "unit.centistokes", + "symbol": "cSt", + "tags": ["kinematic viscosity","centistokes","cSt"] + }, + { + "name": "unit.square-foot-per-second", + "symbol": "ft²/s", + "tags": ["kinematic viscosity","ft²/s"] + }, + { + "name": "unit.square-inch-per-second", + "symbol": "in²/s", + "tags": ["kinematic viscosity","in²/s"] + }, + { + "name": "unit.pascal-second", + "symbol": "Pa·s", + "tags": ["dynamic viscosity","viscosity","fluid mechanics","pascal-second","Pa·s"] + }, + { + "name": "unit.centipoise", + "symbol": "cP", + "tags": ["viscosity","dynamic viscosity","fluid viscosity","centipoise","cP"] + }, + { + "name": "unit.poise", + "symbol": "P", + "tags": ["viscosity","dynamic viscosity","fluid viscosity","poise","P"] + }, + { + "name": "unit.reynolds", + "symbol": "Re", + "tags": ["fluid flow regime","fluid mechanics","reynolds","Re"] + }, + { + "name": "unit.pound-per-foot-hour", + "symbol": "lb/(ft·h)", + "tags": ["pound per foot-hour","lb/(ft·h)"] + }, + { + "name": "unit.newton-second-per-square-meter", + "symbol": "N·s/m²", + "tags": ["newton second per square meter","N·s/m²"] + }, + { + "name": "unit.dyne-second-per-square-centimeter", + "symbol": "dyn·s/cm²", + "tags": ["dyne second per square centimeter","dyn·s/cm²"] + }, + { + "name": "unit.kilogram-per-meter-second", + "symbol": "kg/(m·s)", + "tags": ["kilogram per meter-second","kg/(m·s)"] + }, + { + "name": "unit.tesla-square-meters", + "symbol": "T/m²", + "tags": ["magnetic flux density","tesla square meters","T/m²"] + }, + { + "name": "unit.maxwell", + "symbol": "Mx", + "tags": ["magnetic flux","magnetic field","maxwell","Mx"] + }, + { + "name": "unit.tesla-per-meter", + "symbol": "T/m", + "tags": ["magnetic field","tesla per meter","T/m"] + }, + { + "name": "unit.gauss-per-centimeter", + "symbol": "G/cm", + "tags": ["magnetic field","gauss per centimeter","G/cm"] + }, + { + "name": "unit.weber", + "symbol": "Wb", + "tags": ["magnetic flux","weber","Wb"] + }, + { + "name": "unit.microweber", + "symbol": "µWb", + "tags": ["magnetic flux","microweber","µWb"] + }, + { + "name": "unit.milliweber", + "symbol": "mWb", + "tags": ["magnetic flux","milliweber","mWb"] + }, + { + "name": "unit.gauss-square-centimeter", + "symbol": "G·cm²", + "tags": ["magnetic flux","gauss-square centimeter","G·cm²"] + }, + { + "name": "unit.kilogauss-square-centimeter", + "symbol": "kG·cm²", + "tags": ["magnetic flux","kilogauss-square centimeter","kG·cm²"] + }, + { + "name": "unit.henry", + "symbol": "H", + "tags": ["inductance","magnetic induction","H"] + }, + { + "name": "unit.millihenry", + "symbol": "mH", + "tags": ["inductance","millihenry","mH"] + }, + { + "name": "unit.microhenry", + "symbol": "µH", + "tags": ["inductance","microhenry","µH"] + }, + { + "name": "unit.nanohenry", + "symbol": "nH", + "tags": ["inductance","nanohenry","nH"] + }, + { + "name": "unit.henry-per-meter", + "symbol": "H/m", + "tags": ["magnetic permeability","henry per meter","H/m"] + }, + { + "name": "unit.tesla-meter-per-ampere", + "symbol": "T·m/A", + "tags": ["magnetic field","Tesla Meter per Ampere","T·m/A","magnetic flux"] + }, + { + "name": "unit.gauss-per-oersted", + "symbol": "G/Oe", + "tags": ["magnetic field","Gauss per Oersted","G/Oe"] + }, + { + "name": "unit.kilogram-per-mole", + "symbol": "kg/mol", + "tags": ["molar mass","kilogram per mole","kg/mol"] + }, + { + "name": "unit.gram-per-mole", + "symbol": "g/mol", + "tags": ["molar mass","gram per mole","g/mol"] + }, + { + "name": "unit.milligram-per-mole", + "symbol": "mg/mol", + "tags": ["molar mass","milligram per mole","mg/mol"] + }, + { + "name": "unit.joule-per-mole", + "symbol": "J/mol", + "tags": ["molar energy","joule per mole","J/mol"] + }, + { + "name": "unit.joule-per-mole-kelvin", + "symbol": "J/(mol·K)", + "tags": ["molar heat capacity","joule per mole-kelvin","J/(mol·K)"] + }, + { + "name": "unit.millivolts-per-meter", + "symbol": "mV/m", + "tags": ["electric field strength","millivolts per meter","mV/m"] + }, + { + "name": "unit.volts-per-meter", + "symbol": "V/m", + "tags": ["electric field strength","volts per meter","V/m"] + }, + { + "name": "unit.kilovolts-per-meter", + "symbol": "kV/m", + "tags": ["electric field strength","kilovolts per meter","kV/m"] + }, + { + "name": "unit.radian-per-second", + "symbol": "rad/s", + "tags": ["angular velocity","rotation speed","rad/s"] + }, + { + "name": "unit.radian-per-second-squared", + "symbol": "rad/s²", + "tags": ["angular acceleration","rotation rate of change","rad/s²"] + }, + { + "name": "unit.revolutions-per-minute-per-second", + "symbol": "rpm/s", + "tags": ["angular acceleration","rotation rate of change","rpm/s"] + }, + { + "name": "unit.revolutions-per-minute-per-second-squared", + "symbol": "rpm/s²", + "tags": ["angular acceleration","rotation rate of change","rpm/s²"] + }, + { + "name": "unit.deg-per-second", + "symbol": "deg/s", + "tags": ["angular velocity","degrees per second","deg/s"] + }, + { + "name": "unit.degrees-brix", + "symbol": "°Bx", + "tags": ["sugar content","fruit ripeness","Bx"] + }, + { + "name": "unit.katal", + "symbol": "kat", + "tags": ["catalytic activity","enzyme activity","kat"] + }, + { + "name": "unit.katal-per-cubic-metre", + "symbol": "kat/m³", + "tags": ["catalytic activity concentration","enzyme concentration","kat/m³"] + } + ] +} From 75b38827820814880452b8c5dc68bf55050239b9 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 14 Jul 2023 19:45:23 +0200 Subject: [PATCH 077/168] added zk restart node tests --- .../queue/discovery/ZkDiscoveryService.java | 2 +- .../discovery/ZkDiscoveryServiceTest.java | 173 ++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 24a7863b24..50378d3387 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -71,7 +71,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi @Value("${zk.recalculate_delay:120000}") private Long recalculateDelay; - private final ConcurrentHashMap> delayedTasks; + protected final ConcurrentHashMap> delayedTasks; private final TbServiceInfoProvider serviceInfoProvider; private final PartitionService partitionService; diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java new file mode 100644 index 0000000000..38cad217aa --- /dev/null +++ b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java @@ -0,0 +1,173 @@ +/** + * 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.discovery; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.imps.CuratorFrameworkState; +import org.apache.curator.framework.recipes.cache.ChildData; +import org.apache.curator.framework.recipes.cache.PathChildrenCache; +import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_ADDED; +import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ZkDiscoveryServiceTest { + + @Mock + private TbServiceInfoProvider serviceInfoProvider; + + @Mock + private PartitionService partitionService; + + @Mock + private CuratorFramework client; + + @Mock + private PathChildrenCache cache; + + private ScheduledExecutorService zkExecutorService; + + @Mock + private CuratorFramework curatorFramework; + + private ZkDiscoveryService zkDiscoveryService; + + @Before + public void setup() { + zkDiscoveryService = Mockito.spy(new ZkDiscoveryService(serviceInfoProvider, partitionService)); + zkExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); + when(client.getState()).thenReturn(CuratorFrameworkState.STARTED); + ReflectionTestUtils.setField(zkDiscoveryService, "stopped", false); + ReflectionTestUtils.setField(zkDiscoveryService, "client", client); + ReflectionTestUtils.setField(zkDiscoveryService, "cache", cache); + ReflectionTestUtils.setField(zkDiscoveryService, "nodePath", "/thingsboard/nodes/0000000010"); + ReflectionTestUtils.setField(zkDiscoveryService, "zkExecutorService", zkExecutorService); + ReflectionTestUtils.setField(zkDiscoveryService, "recalculateDelay", 1000L); + ReflectionTestUtils.setField(zkDiscoveryService, "zkDir", "/thingsboard"); + } + + @Test + public void restartNodeTest() throws Exception { + var currentInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("currentId").build(); + var currentData = new ChildData("/thingsboard/nodes/0000000010", null, currentInfo.toByteArray()); + var childInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("childId").build(); + var childData = new ChildData("/thingsboard/nodes/0000000020", null, childInfo.toByteArray()); + + when(serviceInfoProvider.getServiceInfo()).thenReturn(currentInfo); + List dataList = new ArrayList<>(); + dataList.add(currentData); + when(cache.getCurrentData()).thenReturn(dataList); + + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); + + //Restart in timeAssert.assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + stopNode(childData); + + assertEquals(1, zkDiscoveryService.delayedTasks.size()); + + verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + + startNode(childData); + + verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + + Thread.sleep(2000); + + verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + + assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + + //Restart not in time + stopNode(childData); + + assertEquals(1, zkDiscoveryService.delayedTasks.size()); + + Thread.sleep(2000); + + assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(Collections.emptyList())); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); + + //Start another node during restart + var anotherInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("anotherId").build(); + var anotherData = new ChildData("/thingsboard/nodes/0000000030", null, anotherInfo.toByteArray()); + + stopNode(childData); + + assertEquals(1, zkDiscoveryService.delayedTasks.size()); + + startNode(anotherData); + + assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(anotherInfo))); + reset(partitionService); + + Thread.sleep(2000); + + verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(anotherInfo, childInfo))); + } + + private void startNode(ChildData data) throws Exception { + cache.getCurrentData().add(data); + zkDiscoveryService.childEvent(curatorFramework, new PathChildrenCacheEvent(CHILD_ADDED, data)); + } + + private void stopNode(ChildData data) throws Exception { + cache.getCurrentData().remove(data); + zkDiscoveryService.childEvent(curatorFramework, new PathChildrenCacheEvent(CHILD_REMOVED, data)); + } + +} From b271baefd443023d8f58c5fe6883bd18924e0966 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 17 Jul 2023 11:17:56 +0300 Subject: [PATCH 078/168] updated rule node templatization help pages with new screenshots and updated key names in text --- ...r_attributes_node_fields_templatization.md | 12 +-- ...nator_fields_node_fields_templatization.md | 20 ++-- ...or_telemetry_node_fields_templatization.md | 95 +++++++++--------- ..._entity_data_node_fields_templatization.md | 13 ++- ...t_attributes_node_fields_templatization.md | 12 +-- .../examples/customer-attributes-ft.png | Bin 85413 -> 88911 bytes .../examples/originator-attributes-ft.png | Bin 73529 -> 86148 bytes .../examples/originator-fields-ft.png | Bin 70946 -> 75513 bytes .../examples/originator-telemetry-ft-2.png | Bin 60659 -> 128981 bytes .../examples/originator-telemetry-ft-3.png | Bin 78213 -> 0 bytes .../examples/originator-telemetry-ft.png | Bin 69883 -> 114627 bytes .../examples/related-device-attributes-ft.png | Bin 60303 -> 93697 bytes .../examples/related-entity-data-ft-2.png | Bin 76684 -> 0 bytes .../examples/related-entity-data-ft.png | Bin 59833 -> 113138 bytes .../examples/tenant-attributes-ft.png | Bin 82917 -> 87964 bytes 15 files changed, 75 insertions(+), 77 deletions(-) delete mode 100644 ui-ngx/src/assets/help/images/rulenode/examples/originator-telemetry-ft-3.png delete mode 100644 ui-ngx/src/assets/help/images/rulenode/examples/related-entity-data-ft-2.png diff --git a/ui-ngx/src/assets/help/en_US/rulenode/customer_attributes_node_fields_templatization.md b/ui-ngx/src/assets/help/en_US/rulenode/customer_attributes_node_fields_templatization.md index e01d90faf4..2092a0cdb5 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/customer_attributes_node_fields_templatization.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/customer_attributes_node_fields_templatization.md @@ -11,8 +11,8 @@ Let's assume that we have a customer-based solution where customer manage two ty Additionally, let's assume that customer configured the thresholds settings for each device type. Threshold settings stored as an attributes on a customer level: -- *temperature_min_threshold* and *temperature_max_threshold* for temperature sensor with values set to *10* and *30* accordingly. -- *humidity_min_threshold* and *humidity_max_threshold* for humidity sensor with values set to *70* and *85* accordingly. +- *temperatureMinThreshold* and *temperatureMaxThreshold* for temperature sensor with values set to *10* and *30* accordingly. +- *humidityMinThreshold* and *humidityMaxThreshold* for humidity sensor with values set to *70* and *85* accordingly. Each message received from device includes `deviceType` property in the message metadata with either `temperature` or `humidity` value according to the sensor type. @@ -73,8 +73,8 @@ Rule node configuration set to fetch data to the message metadata. In the follow "deviceType": "temperature", "deviceName": "TH-001", "ts": "1685379440000", - "min_threshold": "10", - "max_threshold": "30" + "minThreshold": "10", + "maxThreshold": "30" } } ``` @@ -92,8 +92,8 @@ Rule node configuration set to fetch data to the message metadata. In the follow "deviceType": "humidity", "deviceName": "HM-001", "ts": "1685379440000", - "min_threshold": "70", - "max_threshold": "85" + "minThreshold": "70", + "maxThreshold": "85" } } ``` diff --git a/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md b/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md index 42f4ea6138..5f22261b73 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md @@ -9,10 +9,10 @@ Let's assume that we have two device types in our use case: -- `smart_door_lock` -- `motion_detector` +- `smartDoorLock` +- `motionDetector` -Let's assume that device of type `smart_door_lock` and name `SDL-001` publish next type of messages to the system: +Let's assume that device of type `smartDoorLock` and name `SDL-001` publish next type of messages to the system: ```json { @@ -21,7 +21,7 @@ Let's assume that device of type `smart_door_lock` and name `SDL-001` publish ne }, "metadata": { "deviceName": "SDL-001", - "deviceType": "smart_door_lock", + "deviceType": "smartDoorLock", "ts": "1685379440000" } } @@ -29,7 +29,7 @@ Let's assume that device of type `smart_door_lock` and name `SDL-001` publish ne
-and device of type `motion_detector` and name `MD-001` publish next type of messages to the system: +and device of type `motionDetector` and name `MD-001` publish next type of messages to the system: ```json { @@ -38,7 +38,7 @@ and device of type `motion_detector` and name `MD-001` publish next type of mess }, "metadata": { "deviceName": "MD-001", - "deviceType": "motion_detector", + "deviceType": "motionDetector", "ts": "1685379440000" } } @@ -70,11 +70,11 @@ Rule node configuration set to fetch data to the message. In the following way: { "msg": { "status": "locked", - "smart_door_lock": "Grocery warehouse door" + "smartDoorLock": "Grocery warehouse door" }, "metadata": { "deviceName": "SDL-001", - "deviceType": "smart_door_lock", + "deviceType": "smartDoorLock", "ts": "1685379440000" } } @@ -88,11 +88,11 @@ Rule node configuration set to fetch data to the message. In the following way: { "msg": { "motionDetected": "true", - "motion_detector": "Grocery Warehouse motion detector" + "motionDetector": "Grocery Warehouse motion detector" }, "metadata": { "deviceName": "MD-001", - "deviceType": "motion_detector", + "deviceType": "motionDetector", "ts": "1685379440000" } } diff --git a/ui-ngx/src/assets/help/en_US/rulenode/originator_telemetry_node_fields_templatization.md b/ui-ngx/src/assets/help/en_US/rulenode/originator_telemetry_node_fields_templatization.md index e433536726..5ee59f7161 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/originator_telemetry_node_fields_templatization.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/originator_telemetry_node_fields_templatization.md @@ -24,27 +24,28 @@ Additionally let's imagine that devices periodically publishes other telemetry m - `speed` - current speed value. - `direction` - compass direction in which the device is moving. - `acceleration` - how quickly the speed of the device is changing. -- `fuel_level` - current fuel level. -- `battery_level` - current battery level. -- `parked_location` - precise location where the device is parked. -- `parked_duration` - current park duration value. -- `parked_time` - timestamp when the device was parked. +- `fuelLevel` - current fuel level. +- `batteryLevel` - current battery level. +- `parkedLocation` - precise location where the device is parked. +- `parkedDuration` - current park duration value. +- `parkedTime` - timestamp when the device was parked. -Let's imagine that we need to make some historical analysis by fetching 3 latest telemetry readings for the keys listed below if the `event` value is set to *motion*: +Let's imagine that we need to make some historical analysis by fetching 3 latest telemetry readings in the range from 1 hour ago to 1 millisecond ago. +If the `event` value is set to *motion* we need to fetch data for keys: - `speed` - `direction` - `acceleration` -- `fuel_level` -- `battery_level` +- `fuelLevel` +- `batteryLevel` -Otherwise, if the `event` value is set to *parked* value we need to fetch 3 latest telemetry readings for the following data keys: +Otherwise, if the `event` value is set to *parked* value we need to fetch data for keys: -- `parked_location` -- `parked_duration` -- `parked_time` -- `fuel_level` -- `battery_level` +- `parkedLocation` +- `parkedDuration` +- `parkedTime` +- `fuelLevel` +- `batteryLevel` Imagine that you created a script node that depending on the `event` value adds to the message metadata appropriate keyToFetch fields. @@ -83,9 +84,9 @@ Imagine that you created a script node that depending on the `event` value adds "deviceName": "GPS-001", "deviceType": "GPS Tracker", "ts": "1685379440000", - "keyToFetch1": "parked_location", - "keyToFetch2": "parked_duration", - "keyToFetch3": "parked_time" + "keyToFetch1": "parkedLocation", + "keyToFetch2": "parkedDuration", + "keyToFetch3": "parkedTime" } } ``` @@ -96,8 +97,6 @@ In order to fetch the additional telemetry key values to make some historical an ![image](${helpBaseUrl}/help/images/rulenode/examples/originator-telemetry-ft.png) -![image](${helpBaseUrl}/help/images/rulenode/examples/originator-telemetry-ft-2.png) -
Rule node configuration is set to retrieve the telemetry from the fetch interval with configurable query parameters that you can check above. @@ -106,11 +105,11 @@ So let's imagine that 3 latest values for the keys that we are going to fetch ar - `speed` - 5.2, 15.7, 30.2 (mph). - `direction` - N(North), NE(North-East), E(East). - `acceleration` - 2.2, 2.4, 2.5 (m/s²). -- `fuel_level` - 61.5, 57.4, 55.6 (%). -- `battery_level` - 88.1, 87.8, 87.2 (%). -- `parked_location` - dr5rtwceb (geohash). Same value for 3 latest data readings. -- `parked_duration` - 6300000, 7300000, 8300000 (ms). -- `parked_time` - 1685339240000 (ms). Same value for 3 latest data readings. +- `fuelLevel` - 61.5, 57.4, 55.6 (%). +- `batteryLevel` - 88.1, 87.8, 87.2 (%). +- `parkedLocation` - dr5rtwceb (geohash). Same value for 3 latest data readings. +- `parkedDuration` - 6300000, 7300000, 8300000 (ms). +- `parkedTime` - 1685339240000 (ms). Same value for 3 latest data readings. In the following way: @@ -133,8 +132,8 @@ In the following way: "speed": "[{\"ts\":1685476840000,\"value\":5.2},{\"ts\":1685477840000,\"value\":15.7},{\"ts\":1685478840000,\"value\":30.2}]", "direction": "[{\"ts\":1685476840000,\"value\":\"N\"},{\"ts\":1685477840000,\"value\":\"NE\"},{\"ts\":1685478840000,\"value\":\"N\"}]", "acceleration": "[{\"ts\":1685476840000,\"value\":2.2},{\"ts\":1685477840000,\"value\":2.4},{\"ts\":1685478840000,\"value\":2.5}]", - "fuel_level": "[{\"ts\":1685476840000,\"value\":61.5},{\"ts\":1685477840000,\"value\":57.4},{\"ts\":1685478840000,\"value\":55.6}]", - "battery_level": "[{\"ts\":1685476840000,\"value\":88.1},{\"ts\":1685477840000,\"value\":87.8},{\"ts\":1685478840000,\"value\":87.2}]" + "fuelLevel": "[{\"ts\":1685476840000,\"value\":61.5},{\"ts\":1685477840000,\"value\":57.4},{\"ts\":1685478840000,\"value\":55.6}]", + "batteryLevel": "[{\"ts\":1685476840000,\"value\":88.1},{\"ts\":1685477840000,\"value\":87.8},{\"ts\":1685478840000,\"value\":87.2}]" } } ``` @@ -154,14 +153,14 @@ In the following way: "deviceName": "GPS-001", "deviceType": "GPS Tracker", "ts": "1685379440000", - "keyToFetch1": "parked_location", - "keyToFetch2": "parked_duration", - "keyToFetch3": "parked_time", - "parked_location": "[{\"ts\":1685376840000,\"value\":\"dr5rtwceb\"},{\"ts\":1685377840000,\"value\":\"dr5rtwceb\"},{\"ts\":1685378840000,\"value\":\"dr5rtwceb\"}]", - "parked_duration": "[{\"ts\":1685376840000,\"value\":6300000},{\"ts\":1685377840000,\"value\":7300000},{\"ts\":1685378840000,\"value\":8300000}]", - "parked_time": "[{\"ts\":1685376840000,\"value\":1685376840000},{\"ts\":1685377840000,\"value\":1685377840000},{\"ts\":1685378840000,\"value\":1685378840000}]", - "fuel_level": "[{\"ts\":1685376840000,\"value\":61.5},{\"ts\":1685377840000,\"value\":57.4},{\"ts\":1685378840000,\"value\":55.6}]", - "battery_level": "[{\"ts\":1685376840000,\"value\":88.1},{\"ts\":1685377840000,\"value\":87.8},{\"ts\":1685378840000,\"value\":87.2}]" + "keyToFetch1": "parkedLocation", + "keyToFetch2": "parkedDuration", + "keyToFetch3": "parkedTime", + "parkedLocation": "[{\"ts\":1685376840000,\"value\":\"dr5rtwceb\"},{\"ts\":1685377840000,\"value\":\"dr5rtwceb\"},{\"ts\":1685378840000,\"value\":\"dr5rtwceb\"}]", + "parkedDuration": "[{\"ts\":1685376840000,\"value\":6300000},{\"ts\":1685377840000,\"value\":7300000},{\"ts\":1685378840000,\"value\":8300000}]", + "parkedTime": "[{\"ts\":1685376840000,\"value\":1685376840000},{\"ts\":1685377840000,\"value\":1685377840000},{\"ts\":1685378840000,\"value\":1685378840000}]", + "fuelLevel": "[{\"ts\":1685376840000,\"value\":61.5},{\"ts\":1685377840000,\"value\":57.4},{\"ts\":1685378840000,\"value\":55.6}]", + "batteryLevel": "[{\"ts\":1685376840000,\"value\":88.1},{\"ts\":1685377840000,\"value\":87.8},{\"ts\":1685378840000,\"value\":87.2}]" } } ``` @@ -210,9 +209,9 @@ In the following way: "deviceName": "GPS-001", "deviceType": "GPS Tracker", "ts": "1685379440000", - "keyToFetch1": "parked_location", - "keyToFetch2": "parked_duration", - "keyToFetch3": "parked_time", + "keyToFetch1": "parkedLocation", + "keyToFetch2": "parkedDuration", + "keyToFetch3": "parkedTime", "dynamicIntervalStart": "1685375840000" } } @@ -223,7 +222,7 @@ In the following way: In order to fetch the data using dynamic interval we need enable *Use dynamic interval* option in the rule node configuration and specify the templates for the *Interval start* and *Interval end*: -![image](${helpBaseUrl}/help/images/rulenode/examples/originator-telemetry-ft-3.png) +![image](${helpBaseUrl}/help/images/rulenode/examples/originator-telemetry-ft-2.png)
@@ -250,8 +249,8 @@ In the following way: "speed": "[{\"ts\":1685476840000,\"value\":5.2},{\"ts\":1685477840000,\"value\":15.7},{\"ts\":1685478840000,\"value\":30.2}]", "direction": "[{\"ts\":1685476840000,\"value\":\"N\"},{\"ts\":1685477840000,\"value\":\"NE\"},{\"ts\":1685478840000,\"value\":\"N\"}]", "acceleration": "[{\"ts\":1685476840000,\"value\":2.2},{\"ts\":1685477840000,\"value\":2.4},{\"ts\":1685478840000,\"value\":2.5}]", - "fuel_level": "[{\"ts\":1685476840000,\"value\":61.5},{\"ts\":1685477840000,\"value\":57.4},{\"ts\":1685478840000,\"value\":55.6}]", - "battery_level": "[{\"ts\":1685476840000,\"value\":88.1},{\"ts\":1685477840000,\"value\":87.8},{\"ts\":1685478840000,\"value\":87.2}]" + "fuelLevel": "[{\"ts\":1685476840000,\"value\":61.5},{\"ts\":1685477840000,\"value\":57.4},{\"ts\":1685478840000,\"value\":55.6}]", + "batteryLevel": "[{\"ts\":1685476840000,\"value\":88.1},{\"ts\":1685477840000,\"value\":87.8},{\"ts\":1685478840000,\"value\":87.2}]" } } ``` @@ -271,15 +270,15 @@ In the following way: "deviceName": "GPS-001", "deviceType": "GPS Tracker", "ts": "1685379440000", - "keyToFetch1": "parked_location", - "keyToFetch2": "parked_duration", - "keyToFetch3": "parked_time", + "keyToFetch1": "parkedLocation", + "keyToFetch2": "parkedDuration", + "keyToFetch3": "parkedTime", "dynamicIntervalStart": "1685375840000", - "parked_location": "[{\"ts\":1685376840000,\"value\":\"dr5rtwceb\"},{\"ts\":1685377840000,\"value\":\"dr5rtwceb\"},{\"ts\":1685378840000,\"value\":\"dr5rtwceb\"}]", - "parked_duration": "[{\"ts\":1685376840000,\"value\":6300000},{\"ts\":1685377840000,\"value\":7300000},{\"ts\":1685378840000,\"value\":8300000}]", - "parked_time": "[{\"ts\":1685376840000,\"value\":1685376840000},{\"ts\":1685377840000,\"value\":1685377840000},{\"ts\":1685378840000,\"value\":1685378840000}]", - "fuel_level": "[{\"ts\":1685376840000,\"value\":61.5},{\"ts\":1685377840000,\"value\":57.4},{\"ts\":1685378840000,\"value\":55.6}]", - "battery_level": "[{\"ts\":1685376840000,\"value\":88.1},{\"ts\":1685377840000,\"value\":87.8},{\"ts\":1685378840000,\"value\":87.2}]" + "parkedLocation": "[{\"ts\":1685376840000,\"value\":\"dr5rtwceb\"},{\"ts\":1685377840000,\"value\":\"dr5rtwceb\"},{\"ts\":1685378840000,\"value\":\"dr5rtwceb\"}]", + "parkedDuration": "[{\"ts\":1685376840000,\"value\":6300000},{\"ts\":1685377840000,\"value\":7300000},{\"ts\":1685378840000,\"value\":8300000}]", + "parkedTime": "[{\"ts\":1685376840000,\"value\":1685376840000},{\"ts\":1685377840000,\"value\":1685377840000},{\"ts\":1685378840000,\"value\":1685378840000}]", + "fuelLevel": "[{\"ts\":1685376840000,\"value\":61.5},{\"ts\":1685377840000,\"value\":57.4},{\"ts\":1685378840000,\"value\":55.6}]", + "batteryLevel": "[{\"ts\":1685376840000,\"value\":88.1},{\"ts\":1685377840000,\"value\":87.8},{\"ts\":1685378840000,\"value\":87.2}]" } } ``` diff --git a/ui-ngx/src/assets/help/en_US/rulenode/related_entity_data_node_fields_templatization.md b/ui-ngx/src/assets/help/en_US/rulenode/related_entity_data_node_fields_templatization.md index 8b01a5ec2b..dad46a6495 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/related_entity_data_node_fields_templatization.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/related_entity_data_node_fields_templatization.md @@ -15,8 +15,8 @@ and is responsible for overseeing two categories of devices: Additionally, let's assume that this asset has configured thresholds set as attributes for each device type: -- *temperature_min_threshold* and *temperature_max_threshold* for temperature sensor with values set to *10* and *30* accordingly. -- *humidity_min_threshold* and *humidity_max_threshold* for humidity sensor with values set to *70* and *85* accordingly. +- *temperatureMinThreshold* and *temperatureMaxThreshold* for temperature sensor with values set to *10* and *30* accordingly. +- *humidityMinThreshold* and *humidityMaxThreshold* for humidity sensor with values set to *70* and *85* accordingly. Each message received from device includes `deviceType` property in the message metadata with either `temperature` or `humidity` value according to the sensor type. @@ -24,7 +24,6 @@ with either `temperature` or `humidity` value according to the sensor type. In order to fetch the threshold value for the further message processing you can define next node configuration: ![image](${helpBaseUrl}/help/images/rulenode/examples/related-entity-data-ft.png) -![image](${helpBaseUrl}/help/images/rulenode/examples/related-entity-data-ft-2.png) Imagine that you receive message defined below from the `temperature` sensor and forwarded it to the **related entity data** node with configuration added above. @@ -78,8 +77,8 @@ Rule node configuration set to fetch data to the message metadata. In the follow "deviceType": "temperature", "deviceName": "TH-001", "ts": "1685379440000", - "min_threshold": "10", - "max_threshold": "30" + "minThreshold": "10", + "maxThreshold": "30" } } ``` @@ -97,8 +96,8 @@ Rule node configuration set to fetch data to the message metadata. In the follow "deviceType": "humidity", "deviceName": "HM-001", "ts": "1685379440000", - "min_threshold": "70", - "max_threshold": "85" + "minThreshold": "70", + "maxThreshold": "85" } } ``` diff --git a/ui-ngx/src/assets/help/en_US/rulenode/tenant_attributes_node_fields_templatization.md b/ui-ngx/src/assets/help/en_US/rulenode/tenant_attributes_node_fields_templatization.md index a478bd94f0..71eb28d086 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/tenant_attributes_node_fields_templatization.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/tenant_attributes_node_fields_templatization.md @@ -11,8 +11,8 @@ Let's assume that tenant manage two type of devices: `temperature` and `humidity Additionally, let's assume that tenant configured the thresholds settings for each device type. Threshold settings stored as an attributes on a tenant level: -- *temperature_min_threshold* and *temperature_max_threshold* for temperature sensor with values set to *10* and *30* accordingly. -- *humidity_min_threshold* and *humidity_max_threshold* for humidity sensor with values set to *70* and *85* accordingly. +- *temperatureMinThreshold* and *temperatureMaxThreshold* for temperature sensor with values set to *10* and *30* accordingly. +- *humidityMinThreshold* and *humidityMaxThreshold* for humidity sensor with values set to *70* and *85* accordingly. Each message received from device includes `deviceType` property in the message metadata with either `temperature` or `humidity` value according to the sensor type. @@ -73,8 +73,8 @@ Rule node configuration set to fetch data to the message metadata. In the follow "deviceType": "temperature", "deviceName": "TH-001", "ts": "1685379440000", - "min_threshold": "10", - "max_threshold": "30" + "minThreshold": "10", + "maxThreshold": "30" } } ``` @@ -92,8 +92,8 @@ Rule node configuration set to fetch data to the message metadata. In the follow "deviceType": "humidity", "deviceName": "HM-001", "ts": "1685379440000", - "min_threshold": "70", - "max_threshold": "85" + "minThreshold": "70", + "maxThreshold": "85" } } ``` diff --git a/ui-ngx/src/assets/help/images/rulenode/examples/customer-attributes-ft.png b/ui-ngx/src/assets/help/images/rulenode/examples/customer-attributes-ft.png index ca208c27ed3aa8be923bd1e6dc4fda05c0f56a70..43711a83701427ebaa0a02b514b922f01061b9a8 100644 GIT binary patch literal 88911 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 z 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�!l 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 literal 85413 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
MpPaR?0tSYo68B%qqVF>K)v?j&26nNK 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⋘l1 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%0DXS-Kh`AX%tG(A1~Ey`y2%PyN# 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 z

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 E2c!m7Hvj+t literal 73529 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 Iw0iRY0D0GU4*&oF diff --git a/ui-ngx/src/assets/help/images/rulenode/examples/originator-fields-ft.png b/ui-ngx/src/assets/help/images/rulenode/examples/originator-fields-ft.png index c32cca8eae0835ef46e837343ef715bd579f9a78..59919a33b4511381332430ec4dc4ca846dacce78 100644 GIT binary patch literal 75513 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

Xpc@;CYw%vzPIPex^r4oF28XR#qY) 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

mb&-AsG3o*1#CJ5GI;~zJW$R;>{JEz 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%0D*9x)l_M@)ylLouN7_fbJPfsPoiw(@4Su^Mj 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 diff --git a/ui-ngx/src/assets/help/images/rulenode/examples/originator-telemetry-ft-3.png b/ui-ngx/src/assets/help/images/rulenode/examples/originator-telemetry-ft-3.png deleted file mode 100644 index b0dc22cf8a7c5a9c81363550f96f5aac7cec6812..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78213 zcmbrlbySpX*ET+kBMc=&gXGYif=DwUsdP%0Aks+J(A^>-AQFN!DBT?rf+9!`4Be$P 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 UBDcdQalpTa_Z6iJBn|!lFOh^s`2YX_ diff --git a/ui-ngx/src/assets/help/images/rulenode/examples/originator-telemetry-ft.png b/ui-ngx/src/assets/help/images/rulenode/examples/originator-telemetry-ft.png index 688654f4ee12208b5861b3c392ee8c96358c9141..b50867efbb4c0fec19ea5dd79cc2484a507390bd 100644 GIT binary patch literal 114627 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

EMHkJk`anDm80uugTPEZEkBfmxn5@34hRBe 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 literal 69883 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 R)(!zb20F%n6yflp{{@=rF=zk) diff --git a/ui-ngx/src/assets/help/images/rulenode/examples/related-device-attributes-ft.png b/ui-ngx/src/assets/help/images/rulenode/examples/related-device-attributes-ft.png index 2dc89a27baba86bf12032f0f5711baac89808da4..484520a4fbf3aa81f1396da5064145aa012dfe4d 100644 GIT binary patch literal 93697 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≫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�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 HeDQw(lBJ`W literal 60303 zcmb5W1yodR_clHZ0}drar@%-^he$UfUD91r5>nDNz>v~NDIfx(G}6t8NJ|Nl(nzOt zeh1&*^SqDm?|Z-X|1Zy44y-f#+_CSyuj|_T5UH*zhlfpx4FZAi6y#+zKp=E52n3#l zVgkPj&R_Tn9)P%HsSOSEVST8k)W0i&AM9x zj+0d=6iQNRD4@{Pf!JoM+W6pbd$yH}$D+M_Ebm8aqYwK7*z^m>FeNE~Xdm*j^%ue!VK{@D;VL<+3o^n7yk}Lh=U0sI1%N zkxqZKE6nv>Y!-vVdK%9Y;n0m@<7T?u*Y~Y@L_86h3wS&oJpRTgCMHW9QGDt4xzRoip$L_t9A{MMiWFFrOLBE1jGXJ{-L>29%+_?Ocn5n@lKhAMJOv2W?# z8k(wg%H;Xj_+#`51^!Z%gR!XcUS?)Zs3;V{g~d$e|P41U_n)hYjsc zIkiq0@j=5{hLQsuMr*gLnmzC|hqlbVZ_M?4^moi|XlpmQ1Nb1#e_Kzui;>Xl63@%c z*-Mdb<0gMnO2UG?_+3(NMoD_v&{lHh+{ z&zR5GJCh@TLMMBnlK8h>-~*x&ieeiqb~YN#VDm-~XuUcs`l=mr5ZOv3VC1HIJ%0Ni z!??LU99qSZJ~&*#%QJV}%hl>vZWn(T-p1tEwEecULjR9((4lz}*lOODf;k~RGs>2P zA`WZOch8qp1g1v?Y99WvG7NKYaLJ3#S}2+dWc(0!H59=?fDqS!daWvc{L`p2JsK2^ z*q*>l6W3N6l?6r322S{Wi}~?H^PejXe9od9EYuTiyo)lx{XmUoScJKDEeoHE>}}^) zKtSaGzEI5&I>>`K)UB}AQQhcdSou{%nh9iXw)I*4@(fJ{-+#=gmdIBB!82GKqMCd# z@aeeIaGrvc4&HD$^yzgz%=w(pBIJ)@%z1!e;O{hOz7}*5utD6w=5SEp`PRzYT0+^R@sjt*mnh`OYYGyP$=E)~H9DgJ_B0tFXSJ}_^C zB-5LzLFRTeYk#=^U&s7_jhIay8b8Bp#({ZJ9ExzS;6|dVwvMM~6!_);!z)a{DmWb% zbnIO|%XCUfsP$1lZx+ztjKtD8A&6V*>y(AEo}-O=pqcuEb-O$zB`~4jZ(-s?O3YyDI~qJF;)b{L@ReAP2x9?~#&9 z4DF1gbpm#2BQk&l9ud4ILR;6I?KS7sd?B0|4U+)8a0Ya-jRhYp^XDtjV|XFzb)c+RO=GRM#z8rws=*k^#+$nHl7M$ixLt$Q z6q91II;Bvvd{5H*?BX2``B!ZPVu7$?@-cnB;myvcuDc_QZg>H0%@P(itG9CI}5f*ED$WfJ23(7TE-d`wc2o87oCH`$`xk#XNleBOhcgI9--|AHP z?6Sc~$5EuP-%<9OG*QUiBE8kR4EI&TeXUF`wXmBN6@3JfeL6MyCC}>cE+_3>ZuO;9 ziJGpZM~3W5GmTA^aoR}OV6?yC?CrsOc;_)x0{sio z7nmq$R~t1ZxGwfT)b%*GbnjUjV15Iv)3r%GhrI_i&M>~*)TTQ*{%|I*Ph=p{d4Ht? zegD^z6%83Uize{R=E5!thy~(m*$;mZ4mTOh(@}HI*4@ipDNYrW8z^dB>ApyRUTV6I z?u3R!<%PbM5c*6Ux!5(P(xXtu^J4ib_xWMwb_pixal>DoJP%ydo2&4w)JSQ zhMd?$p?CJEL_CVmvdN>w74)v1JaK~dL+|$@Vn;dXzHf_i&PE$pLUBV8aHD+I^Fo+> z=7TJD>hC%qi_X7V3i;1Bu*7~BY5W}F3G7pb)=T*-jaF%sPay`c@jR_s)wFH&Y9}@B zJNZmB?NmL#Bg`c)K<(%@G7Hw%RH%90Z8_B7@A_-I(OE&w2O=tVB|cg`?*beczt@s` z(`%M8al*6_h_Knq!W@|cMmhtA^wCx;7!PPba_nu#z4#w0{tKQlFH=u8-@P7QUs?0` z0+K;3&BQGKvfQ&=NeIj{zSiH~?zy$K7K#XXN4K#{_ZrBLa%SIYj{A|Zmo+WKJy-Qc z&F-eKw!QsPdzK;BG*R!PHY-6|O(+tdOvj{|pA?&bP+HR{^s*pSl3YT9K*Fnt{nhBx zu^*pizlEiVM05DF38>BIFs|3 zM)-)PE;kPEmrV5<0XI9OYBzhr_f1`{X(67ZzCb}E5|2u@g|c2fz|QbdD0Dvfy83kR zR_CV|buP)l8qG4dBWXn%jOty~^4TYtR0@f!(&-URL8UQ0^05cq%U@sDR$LD13q3!p z@cUMkVK=28i#W27+m<@`kdUV}gX?;>;QsV#@xf`S6=Q)u>w9W3&qpm6+X70aynZS2 z*7yzu0cGDEn~dsQ4Y8X zl9vr{7wu{1YN@8WL85CO*GS4f)#GXP%VKW4kwVQVSMg3^oUMGL@JG;G2*Pjb` z1-}j%dJ7h2+E3+THo2bsd=~#ysYYShVq&Bjng)r-}FCcjw!> z>aU;m6q7VM_}tDo>>e8L>>(Ug`l;ow`id?^_};;tF~?(HUDqG3-W+dpP$*e@E_^KZrsIM#Oy+zzFr#p5?J)JdIh5iqBoyo7fDUX=0kkC(_x%m?f0I2 z;`!ikOa;dM?(BZPksWWUBV8gog9)D8RGEEzuFF8fx1zU8qa}2A#6(Ye$@&-$dsdQ( zBNv#Tt&~ltQ1p6qrTD-9apYk0@CLQaMN8HPr(2r6_Z*P&ADSt#zvrG*$CF#2QoAjd zc;8W0{(zals&~Xme0*hU=!NCi*v0n4T8%g}6Ss{z~Po8jTQH z)2fD(*G1BxUae2Nl0we=IS+PzH5wv8lNr0l^xc^}p>|YZ+G*SY>$B>Shpn0HV|Ut3 z?WIAoGUMFlOBcz8>-muc92Iuxk=ss#Xu3vET^8QR6?q6gjaahR$J`=*n3~DXRQC-J zpqMRs+a5!qO)aBY~tZoMYgozy$GhpK(a{{XJ2reN3u`3>V2h_vb?7RQv&D6CWfnJvAwOcKIf@- zIez=Yu~K&%`!2*GMhwsgj-McR=fzh8W=&JN7w~S4%*H^`Y!(N)n#by;Se|OsXRo6- zm0uCG6)m5t7nddcph}{;0n#Wu2scHr&0DrdL|`lnxm0bUhJbRMHZ9*6<4r|Pnvy3< zxO4txq3Hg~Y6y>x&wxIjnW6$gXndV7WWfWjJ& zUyv5_OMOOlR0Q+vvpmKl(#(#&tsL{MmuS@sJM_W&BFI&<6rzcr%sz1LIwcjUyK{^0 zx$@3;Pb;pM9xYdVnoYVFU9#TD3Ng!5(JNP|p%J79R$r$hUPKg}7Wqu~ecd{}k-2Z& zWs$XjT!6N&22*TqGzB@|Q4|woTh!|=N5Gk}H!U~s_xaVbpM9{QuHxd#1cbS_0!!g4rt~;tTgedwZAv>jrpymdd%m7))PE!2 z!&$sX#dNHmh2M-ANvu;fuH!sxNiXJ1=71yI4EXbt?ju}i+j2v1;Uz}FXI#osoFo(( z^&2PesVM8k;a}F-N;ubS3O-kj{MfQpgk_y(Hm9@6Jy~JsEFD4}Bj#0`ffYo{biqh< zoS`Lqgiz&U@NKYUovBuynzu`4q?blboVXp%gs2C5qAdjOR*l|wI()|H|&pG0OF=+(JlQzBW1^LNJPMd`el^VE^gls+6UgB8Cm&RKg}5nH{?$4 zU!F)LyU=nd%1oSW=&rvLw>uI}Z+RYWHK4;6#Tr7(#lp^0h|f2KnZyJ!Yj-?vUJ=PX ziEB+JC?jcR>(PdnTWk=V228(^>TkOvZFYz(wZpuF$tg`)rSZ|3)QbGBT8{j;PbvC7 z_R+6kX^nQZxTpTrR=n1MJd4=5MOqv4>pOU?OGz{_y@jb_)Asl@*Vn=pLfYh^>V?&< zuRcVZ(O(>lRk-Z1xGwcsDVf4BwW?)2PR~&kYocDIZ|8FaI$BmAFv)+=swNa#3jc1( zOrTvcT2A+tgj;@o2&GX$PIQC>H4&1Z445=&@m?E9X>dQAoK1a}I#fC9O+t-@rdvjz zsPz8eY0hLwrxK2n#<66tB4;QjysB583icf~{#`+V+;dd`JB2M2gr?^FC_+BDUaRaGtZ2)$M2kv9<0yWd(zY?`}&|kpp96FP!49 ze%}&E++1fq9VmuLD({?i|Inu8NxGI#rM|;x0a-?q3QF)j4%%wMRe{p|q@W=zv6XGT z>W~~K(XpNMTnMhZcbMk0I(*48O=( zeBkO$Uk2&)PbdSCKQG))b>gihl&IxGqu7la)i9Oy=egz3=NX|-rTQV#g;j&>4?e8e zsX@u>V^0h$%6^h)amvnMUIs%&b&34#le-;1#7M|_#1ltau&G74r&07%U=%$!ujVXA zT^@64IhD)AP(S{zw*9urS-T-RS!ry$@T>-j0+0p<&vj{WuvwnkQ3sB7Emh!S^{8c4 zHigAbjZ@CEoPk+xHb`+NP3)pPbo?w9wXHu}kebR*w8IHEJDB;F^>kO!Va8L@gXT)k zTXcEtQkyt?{6*%8N#iN#WUKY_XV#4<44zzpAWv`PjYNv*Z1mO|_&ZHWa2 z9~mL>_K&#@?ev>3(+;SAg@0b8SW7Aii-%?LYA);;+g**$(Z;t>J& zuTt;pX+}u&jPGt=Rf(~Ycff-^Bo+d%QLQUN-+oKn-l*PGKl~;)H`YyaHK$7(&g#VTa62Xb3~10A-w=mN>*lveoay&pXcHa*{i&p3m=} z4dOa>43Wfv#5Z?WeB%&@sea9mpk`kP`dD-5B*5N;vM&hWh(15$BRCBW`eReGleW2s z-Pg+?DQFNufGP1;R*72Q+Q5&b55@J)$5y?c9^S!1TjE2}jkcvz-}#&ZlXN&ZGo7Jm zK6454aB!(!A>eksLlAyx{30q}6f8R>NrTZJII}Kx-)oZfeX8`YD!OTQ>jP%HKD~Ph zuMVN)TS@NktMe?8{n~5y`}bW0E`3I3m>?blXC|Sn4O8^6XRIU9_Pg<{l!^>a!mgXDnW(VIUhI6tk@!)f|quF#O zL2-l3V51Q zk)ZKKN;8KM)cul%vR~%bBC63JQY_sJr9tMBuLI5%?~q2e=?TfP8LURl&?DYy&3j7^ z1*-RTxy$%1R=s&$z#l)2=ai-s-(=WqdHa2Lo9xt}_2YQrXc^W^0q!{#1W(C8oMfAT z1-Ciz-3!c&19Pd>G7k7)m7>&|R_jYjU8v1BS{uFNNkR@+nL{D3jLIQ#{W!d@zOD-w z+D`|6>GoRtU&Wa~ZH2gN#F?#~Ugv%cM;FG%Ya8y=1M9Q-;J-_u$|2*}z5Vp95V@M3 zIPn0d|9(sJp3TzC=julm!Yw`p9Ntml>z%}HJOd^T^P59`TQ^+A>=mMU`C-Vn-P+vi zleyl#Pft7h#A8#S?#m+R*0Ec*rr!V&h+@zc^7D=tSugE$@RC$rH=5P?Teo5JxlR06 z!{H9LN{`wGuia#Bk$jHX&7SA|-t}#TYpGWeW8~E)^O<(YS#=o`$qUQJkxo#?Nw>`= zgW5Y;7*(Mbvd|mZsH6t1R#+$S)8sF`eAvcE8YYsyu-@Z$fO=FI^U-arH@NzI z=}WIJtIKgCY4Qj=r;h1IT_DVm`|I&R%}MlFtFFs>m*rag3@b6sZT8B_e1e z=$3pg1kEEGYVAiVE2KrFE#e4FKorMtQGX@9t z-YCA9$6*&OvMJBRKO{zfd=*qQMj$&=@i9{?aBf}3K|H0BJ&`x&U`O4UB2ZNq@BVmL zfWGFwlxObm>7#@TY1|ljEp6%YzSch3#2?Z6wXgle9-yo zz#v#$N2|?~4OO7=en@eeax3ZZ#8!)?^(-IKj+x>t=5;1feMS!97|$Vg9?LE_e&2q)Qy+3Q6RY-@v|7}Y>62hCDGbB_fHmB zU72mi3hIDPJ9L95d*3i7E1#6UB^)pgYck~52^8Dgsc@8o=$CNR4R~+M-RP%J$z zD-GJ>q!`@`*&$vXgLyJR;zdt3@MDtEV!)evSQwb}S260br}jtVcgAFqAj+VjYq#^l zl|pVfTbhqE%MVz;TQ%B9r_&mt_06%lX^AwN6~)4kCA zlr;D%O9}`2cHKF< z=`SbAJII%HQs6I#^08vi^&g&v)E$%Fwm<%)_pS6b<;G4K-cAu8+J~wDc}f_ z`tsru_fLw_XK_*UjZ}I%TBh8$30{Se<(WVQDFbD<#lE5kKQU>&l#Frr+ElN~plrma ze)hY#B|wCdW1i=$(cS0Ye9sSW8a7!Iex7AuaG;rT&GmfoxK0P%R9_7N1jBOi7If#De z2*+-(c+DLhA5lOCUS)8_Xb@nf7vjw;_tPh*J&;;Oj{;_J$x{@buCOFJ7h-a*W}8tEB;jo6rhn(&D09 zF3LK-_E{+SGky_z^#>Cf|5co~LdUe?$zq+?$=xsb4Rtc48b6&@lmi5;hWp!Ov4MGa z7Js@-EU}cok=>6{qV+{>7#%Z!=%Ys_t$xXG-LGeGX-Wem*@u8S_U3GXwMEZ9GZ0ZG z8#vVzoTor)TGIAyDZC)%EU$n=zM-vYZv!wPj+bK$DHt!r}+7*YiP@TaezWURmBgbbglHsZJNxc5Bgu3sN4JySK95 zd@Bw=OTa9FUCQCmTr@gVf`3!@@*5LaS!}7E_g^%?%qfvfMUV&#b!#V^k#PUCKw>Ju z!qwbyHUB(PJt7G0j(ykMtZu=st6FN*o7P_l!$5uVz;8HchdV>Law00E{2K(j2UwN< z|EL%R#Y=%OnVB9rJn|D1Mpr(GdDFTU(p?eZq-iFG8p~Bm%jL@)y z;ym}f82)Ww7Xu6>TA%z7{VfAcCS)$;ZP!;z7gb3jFbv)Nb6B<4E%tfIfPfyu-5}WD zK6<$n5%^^4B4nqHL+{_^${V@G7y@gFENfqU&Aq0P{)^_EJk*x$ z7NDf3y#4g+-#4QXLcan9)+&+^2XBZ2HW~-dxG+1^0wGQJ0G}+2>K~OvNm~GtgTI@g zahRW=$04rl+rFEU=EI;I4?|X@{!#pby}|SOJF$-m2<$cr;Da^&!1&I-GNR>JrcF>?GyVtWDfbrM$ zHWNV)&n}^}B)|e5@N84*mv5!S1%thM{vHkR0iK93 z0ccgWUOWu$Ur}BX7X>VO7Y|AmC|~^DF>peMhK)wsv?^-#k13-OI=_ZVM!>FC3ZTI|m{ce5PI3eN4i)FruTyYs~20P1Y^M-5l9i*Bxu z9#L8Dg~be!?s#Oy^=BcL0_~x6w%QwU(m2FVia~Ma{7WN$@tn#ht|I@Q{mn9KF5JSDfELyx@Bs9ilU-_egnY^M#sc0PiHU20%4=sKYC&~MfoJR#yw+9EVGEtJ zv!?q?-HY|A;cY4K5jg4f#V<8RNKY~w8TLTpdzrru1k4Cb4F1G;93TM=dM)6jc)b0C zgQMB+RHGyIPEm9@r*VT`MX3tSdeU|&MRMu$wja`{=~&<=%1xk*Z7iTks5Co*c%BzWyD6l?)tw|!``+J5x( zZHb`Eg8bU>J4VfXWjk8`2$BGNC{&$Vy(5O|eSp)*hlF=UWLzdMN!WC{mU~l^Ye8FD z#dm%UDpeJ}A z54dy8r(xic4vF#M+&M?n@Hrrw>?6JulNpvse{CJ0L02|keeEsRS z{Ny^9#Yti?cMJTsM!s@Ev>CPU;reJI^W(cuCo0}8`_Vznrb8sGJGhJ+^3HB2Ang&s zelNbfAY=P1Y~F@c{D9h(6?qeQRj3C+itAXQ65_7>i7T- zlL+u*F{t{mkSOZy>aGG0h6RTOrw1MsJ^RtH?pMtdZXaVmTWT!g9?SmkI06EHmj(rX z%fYNN8o_U2FZKr##@r9(8h6`x{wnTV<7`PZz9gKFka6za>U75oJXuj(SB_2)&wg4= z*r#Z42-#gUOs1%67)qHRSIn;!?JX^Hm%gMeVf1F6LQ-J{MxTl2*5qhnWqIIr0AEPV zrCFLGnF`b_%#ijq{tmizPLWPYVLu+m$4hQW9-K@@CDPZ@pc(AG^@r-V%bm1=S~Ry* zm?wau%@LH&o7`fqsH~*|_q*Fle>Yd_#oe#3_%ypGnPFhTYv9LWSLG^5Q}T4fDfp6d;1)3mz0W~ zRojiSqUk~*5p2j?qaPkWEH$dX`(8COQ8iPThdp8-;P2%22qBGHl5_@?d1Z@gQjYfb z0hebzdf&0FbInKW!kF8PVLbq>!{__c7&Tx$Z|N z>ufxi>8$hf>+5@CWv05Pv<5?>lCq%)wlP^E@a5T|=37&PPba8Z*g~iazxTdnbweW4 z1A+j|GMt)_!(a@YmxyoS(Ly4+&+h$&$)L>;KMg1f=C?Z+Bd3N0_XS>^%2C+WM2XYT%=1D|PtG>VmS+R6wOw|hIK$Jt-)Vw?kXS%sUOpV# z94wrcP^(>Rb5Sh}kc@>3V4O~s9A5Wfc9u%O%?y!+@1f-=5%Xe6etsVz4bZT zENJt^CgY^eqXj~}PW`Q2UH z463*}AV&54z+-O5s*BNx{Tuj36Cmz@&T;rN0uuZ6*xt0%3}B%} zfcky{2VV1Q^ef{Rvso}fuifmvOp&of_TtbDItX;0^(YmuUT}Ug3 z&YY>Bj11o?3BarOM8URlQ)(L`lOY7S9kNJ7jYK;YApBLu)W9(vNW36(=4;dSl@9@5 z5Otbql1kaCQYH0Ro)jbm8_Zxe(F0joGl$^ri{Is}8z_l#gTR>LY(X--bFUTP+PEhf zFv%`lLbC*;jaUm{u{$U#&$wR_q~ec8sFQ!)eRRKA$Bf~#^S82vV8NYAn*r7*mB7A9 zCF+y_#l(s|fhBqrOioI^3yDQE($b|M*~VkNH*bY-9mQ@BD!BkT)ZT+sA+0y6uMQpc zanLv%WZ&o_j>7S%@~sc~LTGrl$m7#~eAgk8Hr-r?&iOrnzh8!~9=m^`N7Q_zBqA)S z4n{gU5AQ>ZJ0nS7UvJgUvPuEv139zy2m;oT55U1JyxpXhD;uK$)-vSlO11}T0FvVt zMsLjyS$w-^x5+mgLjD3Ubj)b%V=<3psxVZgDQVvjf%Sx#6cQQo#n2{0!(fu~3P4yM zt%&D#Ccwc*pFIn)2)w?m#Xbz$dx6yHlYA0N%TZ%uPb7pczmNPBG2zra*7m(nYL|`< zjWn`$72P@v&RM1Jdw{VeRkHA+6D9@p?WH#R9%D!A8}cjy?sgU-=%ZfY};FCfP+@tAJS?rr0k_i z_kb=7Mw(ah5_x1q0G`9*31t-jxDHFS8zMbJ);N-o&&scV*Rg9m3>pOA@{a`(dMAW{ z-lCfaP53%@Mm>H7bN=lYtF)qOyOR1VZpf-|f^((+fm-kwkOSp-(4<`)yyRN^9)vr$ zByLZ@_tU|~>}NWp3-k#&Zy$Gm2~J88k`D1Wx}3Is^s``%$=IFBWxaY#@IzAve>qSQ zDLezvB_JT&r^u*PJiofq@?kttJnlMpm|6DZlX-b4-5xO*GqRPt?PDTdVgSKT9N=?v zCwlczZ~TaY5V}w7;{Aq(1X$<6c+qyw?S+!e{VnvMpj^PVqx}~WIEWO+cz(2{*#;TI zofj3PmBR>@Qab23eS*Q5@x}y5EkHW^z9)D8ek|Hf{FMl}zcdCAOOnIsOsW{Izs5e@ zR{INC&IzM=PNkI6PWY){qy5620AuNi*X5IeX>o88Qwee`*={X8ISlIji;9{uO2T2} zAFR^}&|}=!n(L8EFP6U=`8PY&q~ISuNaD5WXLdqwi;J)8iwpIBB{=mzu`&oB3u!r( zNP`w@OX!Rqqy~q;?uWuSkzP;D^`8ln@c+#;%@CkakQf*<45#Ry{0B)2;9r0UTCx8U z{U;H`g=GW(xVeq=lK*mV;B3=X{r)wS<4O7-9QL>IP(;vtTj9Txza;o=DDcc1s@nSp zRYgOG_{l;K*1p|?8SOTMi2Xz)&+oj^>zDdt^Hz;B7bYFW*B zKvTN_{cC__?oy-m`8|px{^WKGMj%cl7L^(UaAGe6LL@7O7%a{Np^@;P4|?0hbOUNp zm5?0SqvKVy6Ndw46Mj`-d~mS!8{kG_Fb(A^hm@N&nU+Is_V(+mF+(y47CZp7;lyBe zG$9WMm~G7BEC0JUg-bd}U(063d>+hMHew2}+?0Tru%QR`5g`z3e-wFRh8ZG=`8(8O z0n|p~kbq|cKWhG=1@lFA+9+tDnCuM;FkLwCz0}M~x1W_m6!4qWzAJk(o&)G0{ofBm z^Z6f32pY6T$cfdw*))x{P4o6_<2@p#=v07^G3zQvEwSXeCKo1(KEhUV%2!FhTfe`Q z(3j48D>uBx6AKqti)!xdQ>(zU@$3LD{lb@PPegqVsmW@JSR@Agq0Uej#z)RjkkZ&hVIqkk3x5wyJ=y!mabRkjKG?Du*AF zC0i9dguB*%&DG4-?;-R;+_CT_yRj@dM~)0PyTtH1Wq+AX{)k#g%Mf6;FHiOs6pF_` zhE&C*Mw5QFTFt&&VSQ-ly|+-Wb(T{Rb=&|nQWtXYa@}iM;xukz26$uY+PAmVFuid(ITDhj+l*i@(DR6 z9FS6@7WO12m6tm$I~)Y`vRX-;xA3XstJa1UUNS$((EAd~)tF(c)NCwK4ek>n^+DS*m5vOMQ)iqjc)=R?{2ys+J|1 z0&r5LpkNGbs=({!M>NxcbZ_1((4X$F)E>-lrt?-f&6*tges`F_sI>1*b(7vr9nENmD!BpTt!PM7V?3D@f@F@;odpg4@8>!{pbev#TU*de1>=R7~c zizV@E=_?x`+9bQ%k1?YWh^SKcvH-_jy2fFK9Ssd_f2Vb2)NQX)t6&7u9eZaABZ*qb z^{2*w8ga86Xkh5ud$r#Ahqso$ZU`g_+3R$t@c^XnK>lZ6R!LT9IdkdYZzMeZhv3Eq4 zj!F#I?*t!%vW^d+f>g`XG?Z9r{`?wHbtriQS8*uFqocPlu(f5l$s6bJKBbr|l9L#saz^Z35I`ByM?O5(9BmHRdKFD0rr7+Po2uGli?YD5 ztfl=0l`bHGd@zx=>P<;X`)JYKL&ChCWp z7SDway&u3&r1w=Nk*#{VD7zQ8T4r?Y<#O{nLH^az>**$UO;)WBw!gkuL1An+F%tPZ z_NdlBg!a7=P*ah~5OpzXc)n4Jd-j5ilR1tli!U;XK}UM7<8b4?2QX@Ki}q+1wp6a9 z`&PXL3K90YWvq*hE=l>W2dj^*o{yE7no>*{cpq)1_r2nI7?J9gW^nXQ6yY%r4jtk^tkXS`86<)_MUWFehNMv&Q~$CI?28E54e!3 z4%t-`^;CN!%<@I)+KAn_(L-}fm=L`2 zy)=2TGgfiP;>##REjo=S&#rpi4$Q2P*JZbGYhVup&@3_=+!-zMSNGk{$V`kO03vx8=41K*o40TR!-Bots;TFn=`!xPQ+ zshi_1D#Gq-g>BCKPLp{i;4Kl4#0g#-R{Cb0E#X474>(WmaLM`G!lZ)_q(S)C&AuK= z;i{S1uhSSQB?2<4Y=<=N%EcC&#Cm;%N3X6YvsE0FwdOru?C2Tw|9S9K2vAXcytikX zm3orP%v{!}z@Kk4VH!rWBde8Sw`~|W?Qxk#b>aOg`;p>zOd@8;&KN2+ZXy~9DZfK@ zBpg5F;!AJp{>22Z73iVlOEy!J8_}gmr&OJYHjS}Nzo;fpF;iInm=BK7_oYU+Ix0@z zUteZBtxzB$;jef8rsn6FcZT2_UG$$&3QVG)#kuU>NKs(M4iZqwvJ&_$^L@Qv)4lR|&zk=alf zBU0s>&}eW^5F-~vaAe=Kof6n6qrbMcRyevuR!Jnsfzbq)ht2ySA(i- zm@|J@y_gmu_qjvf4eWaU1gLbUU9O$~Qe*9);bOOsLv4(eztR5`6&<3_V#8LD$3;`2`6ek{!h*)5hLf1J}E$>6amXvaiDL_)%k-~YY zI#1!ju_x@P>Xmd1M8y>h;Dg=$U9g;)%M>%2!0>7 z>d}kEc}VM$52ffyW2y8h0Qy(3T^S&A1}*;QDcBgpM;l|uHU%cR`3#C_gKvS|+%HxJ zGwgOl)BGodHtIw|LAa9=WL!ElpuU#Ri$YJ^AXX=zxlLQ2Wo8HlD0|C3p79n$=Kz$d z!DKdFc_fq|PjiLS?x&%t0~VTcK;G4bTX#Bd>N{3kfATFT->?qenQt#?y~|>Q)xy%i zmq5K}2cZ3MURIB%^kH}`tHbIKp{uu(92!-&XXHSGb3ZxTS+^pkm&L93Kd6M0DUqmK$&*ysZj{ z2`Lh%3)sYF2>WYL^4qcNS6E`*g_NV(zPHxO=W9R$CZ8t)qH?|aMoO`Mv!Y7f9Qmg`D2hnr zS)Xn9OQ9D_ed(IZq|?owY8vlVOQJ6x-oo#hR3sPyUy%bYeORPWd)&d!!U@(_ey9k2-?3G{<2JMi* z4LrcYvX(cJO{54#yjAM!VVFz)jrTKO0&b~u@@WZUVW%8;Aq^V&T;SDS_^Wgv z%t)44hfVk{G0#&(Z0QYx%tc?2a;l$Vo>|2>Vxfsj%s&;p9uVS72!2GOn8N-J*QXig z1!#Bd#uYMvKFk6XinR1#CdhL55~EpYuJ7@74_R{22Z*_U1$WkG!49A*Fv2TrctpKE z5ohD=j0E*lbdSJzpnF3z_w*EQZ_z)8;>f>b)167QN5nt=%C`ISt3Amp2tkM^G!#+! zxPR;IUnE7+wu#G2bxKA z)!u+4dbG*`S z6F3qDRD(u@cVn@|W3_YI8@a-6)!rb42yjc$=#BUrl_ka!3 zpCQgMR%%*FU1>Ph4+Kh{0*&fd%TD#K%PGu{bI8}pZbA_9Hqh9>Z0!avetI*%T>GPm zh$BEqzDLvwMd&Cn}n5p@t(4)hwX1IJIIU($mo`~u!kYRVkhNKwv8srJ5ngnLz(2MdMws{4tHG0sy*I?j(r-V?Cky&2QSL;gEw)+sP#* zz|!DvOAr3G6f&3vlY~BQ_NivH={M4N|4gjj^SCschEHSoof=o`760f6&rSCUVu#?y zA!VbbQU4_JHz6v&qgo6;Sg(0Y$bbDMyJ@{aTPV)^*pK#Os!xZS3Y0SrUJ!E{wU4;` zHska!GcW#&nRqTW)dJ)9rO!96wBwInka4hU7wTB42C=M9r$hYWejhxG|9$Yd`AQwJ zf`DyMswqxidhO(+2&8~pPg^~{t|zj4yK)xTLi8OV4n1lCidvO3;29&XYn zqTur>e#xpmxs*_U6Hd5(FHuL-pGzc84_QBYmW@Qc3n5$HPf$uM(lLo76`%}7bU$`f zgJuZ3D^iY>(bv1JJgx^%7D{z%=Ebk^S}UY+n=8|#H{Q4d;cs^+{!{l^<(1B!*ws;l zUQ}*mTr{3f-;$o&|9EZrZTfJ=D~7}0weySXac}>SC*(GbC>^~VZt7xZT)_^MnCr1d z%h?%KB+v?=>-IZlHT~Y7N?un$Et@59md&_Pj;3#7?9iQ>cOw1tf{wW4+>#d;7g|eL z0XephnJ1CF0T!=mLs25_+W`|8D{U^cH|K#{@pqKkJ8^jahx~*(w=};AI$r+@#JjP- zcOdr99k{{He+Obo*l)z}{|GLUfCp2f1EGLn4*9Q`@`EAc@ILZI@{R2EFADi>?*FrV zRcEKp{S3d~2Lqkb6Hs+emLProK0+dB;#8Uc6x-qe6?R6jbmHF4bb+3eC%-#nRtd_L zvTX*k)~V1YYr*P|&w-a0#vL%#|(IKZ z8_HmWEGIk>e-I&!q6!zdiN})Qo!=;SQ64_`&n)tW0Q|)+Rb3j`VFr^zFG3a{hC?<$ zF}(dhieWI2Co9}KlKz)tzru5RheQEV+bkABU?*_+-`e2+Zy_?ol=R3w4i%A*>#cc{ zi6tZG2TSa2;*Qb>WaAdB7JC)oedFe3>Ck{=+!w}r7c{#CL7 zzQHhe8}Jfh@HV;JzY9G#N;neei^+yT%^#-xVEn6Wc@xiX0YMrY%`=+5=5Oi!1~{|< zQi?hh06@Ll|5UZ>LT(gtgh&U$AAj@&Y7U?dpj~J@-pUj3#T%{l_cbl`J7?WljBxzB zH46BEE4{!ty98?ew z0tb;01Pn@)MiCL|mXK7mh+!=Yt1Ti8Uk-g%PG%H_yE5qSUG z2W0}`d*+DlQ}{+d;btU26lA_V=GPmeYZhv;MR$|2qosFE{xpCS3R? zmWO$)w-!80?GIR=buq6(&u>Jctx`GG_P8LUIDk}%IiI`qhww=A!rIF><0VV6=jK8cZ>S(Hot!4 zmD*IOf8C(W#yr0t<@!O%SyOip+#mF^04FM*Lsfkiq$iR!`Lm=Fx7MSER)<&_$@j@N zeYKh9VR!G%71E#IZgmljxJ3Nsle@kCK6fC6?{w}m9)WUC=d5ce)p0%_tmxvD2O$fW z!ANF+`~(%N6rS=y&z8#y21LzXexO4&7Jm0kZT%LNO8rYHc_T&uSJmxJ)@do(?|T?d z1(e?f>_s>u%2)dHwhWMg;-OI00UDFI`ujf|y@|Y2X~c!S%;8fV^1E%a+dyId;++GU zu-BGGK(?NvjmPX?NK)oetybNX*2P`b6+Y+%p zz}pcj-Tl=oNP3@Nz1Fovz;Ee#b(-W2$NEHasu z>FrBXqW?lE7?7*m-Q)&aq~fo8VY1nvCY@mQ>+)p)%1W;SR#LWQJK00a?bKQi5?SD% z-9o#kPRw!YB>ow=%J1)Nv3(AWyz(gD7^GD#glE%7rvcIG{<{G&R4gyxO(a4s8h%T+ zJJ!GY$;XB|7|Ha?=%Zxi-L*-7Rd)NO#H$Q4ueMi*Y3`}l`6Txy(=%PfIbU!eN5fOm zc3XjoVmp!aXEfJ(T!}SbWHmXdx+uThe|f%dioVEVEBP||_|7r!O+Y6@v2mjKF@;Bq zw{GcXzrJtP!GrRTP~7??kM_qfq|_@X(0ZoQD1-p@K%V1(PgRE ze{J{@(*Ssa07H7dJ{~6wjQ+qZ%?ujPbvCWs>1Gw4%hWGuK{)U0cU$6Pg@b_g(S9BZ zRt3x~rnghyAD9`TFpNLCrCi`_g{*dB;XHzX7sY5g+Y<0v$oDpZ4@i=Hg6~e&bJ1~M z(A@2ykVpmy_BE#P&*a=M7kbiY0P@hodh15LsE=b5uUq;Zlxuw^n!AXzFV`6B=DYA< z|L9=Le;`X;Xez$7$0RyfFj6LM=<6N%`wtzCOH<{tUI7+L%4N4n(uUsww{oc!h2`p~ zZ%>k>Vkwr}OCOXO|Fa(iPxUV`Vj-LeeXivPH&N2y;Ya^+pG>tHh4(yOAQMww=}RLj zrT!+o)Vh}q%BWlzdHDl5r%htD%YCh;TOeJLAXwSquX#>D{VO&llF$K;H>#?UFT&Zy zv56S(e0!iRBW*APC0<}Y9~~IuJD#&?Gn^EnSXWwJgT1+8yriI`tk^Q?J z15bBw;bS?mPG7!>m9R%9Q$Iz7LfNwfyvatpnBIBp0%v=nM*VO-GphNY?W+88qN{iK zzC|16{OCT82N^?l%@~)}rqsTOGIiKOpkJ)Vn4MhjizpP%$ZY@kHU!uF{Xa8>yb7PY zXI8$%^(!Ox`3>2+carVt#E#H8P?dXVIukJ693K0aSe5HPJ&S{-4#=eZZ!6)a=dkp|EtlIRA*B zVj>zo*PVbb@NL355|uMJs$E6dL2i(5o?CZ4zDfuaaq zUz#F4r_DPCkzm%GhxRlgp~$H^&o6*<+ZYyo=V*V2Eex~gXqwZL$N39p3;`%jO;6p{x*Ph!53QxxvDn3$Bez^@K z3J8WLh2Yu0I|Lp56AqKt)p!p>#lns*<@{0X3#CXXbTOnhf;s!DUE0)-);R1E~KtTCCf{R;t zfAjk5<3sEFmMaE(Q{Gg`jL!qxNN0AlG`fm}**qeUN$=eFc1q6b^Sh zQopCLMd2!E*VyO&dL~nK=HJ-WLLXc2Gk<{Z0!x~0GGj77lvh$K>ceAyr1^EE#8y-$ zT{-2l7%GE82uQr_Z>OFwvKr1zy13t0-xU)Iz2(so%2E$^I9EI9M-jjljYjK|&uEUm~t&~BLPq||I5ySQK+EI3iJYJ*BSONFd) zO*efLSt}^sT1cr_9xV~PTq~;jt+#$0d%Br{K_Ndtt5s31wt8MN;q~B0X|R0k!YBLN zM~ycjjE4sqQpr-vd1<7pAR8r-Z*coM>$_M#2E~_cCmY^U>9548UxeNPOg`ylgRxQ} zt@hyCG4-E)8@^DI6l0#7KDZ6^RjG+^UE3gS+3>rw(wlPM0HFj-Dk`8O;wjF&E&;HG zWEoWgFg_6pm*l6-0NrxmuI^5+cd@zN^JoDN&Dw@eiJ4CyzjE={A)|Ov6i^jvQ9oar zmkpyOmeak9{p;&9!h;SInY#xEitj6TFZssmbgBoKHiyxdFzfg64dv@+9J|^5Yz?EN zm$~DcBAec{ajks@@Rm_2+WD@$l4QwzGojn!*|G07mQMm*N3%6^a*g}zZbE*xePcK} z*zp$$ISBv|y`qEc|ovM{^-uGOfHzsWI|8hq@NB(_f z2ZhV)nU93oZ#TvRIwApv-q_B)bMF>}jML^8#`>)Ir84t{zMN2ptedVN1FJ_wA(sB) z_H95sdAGGyVBuPsnsz1c-~l!~2|(fBF<@t;Nj9~qT1mRo-h z-<=(K2vYuEgJ)FAY{C>o>I2{d`G5kVJ1SxKI`z%u&88sbVl$74i?4_CSN(|ZsdKat zbhu+u;6jDUmLHi(DSISI+WRiDrK23%nUYo8lqqtMX-kuMuOXc-A)T&L5-$U@Ih+>q zk#_NU;Hb$vY&jPzL&fKO?{d(WSw}l#kWT=U^A-2{10feTPbr7qU+GG|()xY!m%qT2 zpyyf&iRODItuP8vD4JQM^5~G>R_JX>hl0)eAxcNsv$iMfczwt15fzT_#EU+@xWWB$ z9|qca+}AXbha$DeDV|_$ulhPV66?u}zH4X!DaFm@{_!p>D>^_fN)kXXrxXLZQ2#5L zYExPsA`!A8v44NF6bN6h{leUFP+3 zkLjN+1x4e>I;{2oA{wbOiA9cguw0;Wr`g~B$I7p@zn=FkxWQQ*f$7b3$!Tk`zj4Cp zVytFL9}4Vz`1Y_6Y!Brb0v&5yw~2qF0xNyqr;n)=q?%~vQ>}DFdw!vEw}1#8eu4HXx=hU%GAC~v zhjrESHJ-TldrtZRm<8)ScRWzR`sytdswGKkDwyk$gcmgMjSTI|+P~O>kE8JwfK1^^ zdDN7DrJNY93Df>dN(BL2yXt82gK;B+!h z3-JI|@y7OlEX6}Ponsj1Lja8;pPlLqEO$=Pf5L;)i;k9PelkQlj0;j0e}EY$QPB&^PPAWc1}%VWr}rhNdQp`i#9fdT z`5oMX7gs)l3V1FVCsUqgWiUZG{@>dH63q;j@$iAdxu}|9=wxLR3wm6&vF`0rdFjQdZ(^(9FA<^IYyKIZp)- z*Yg%izevrLSgte#Foev2nk9*Hz9*NU(7R*Moj>Rem?kzL@2F@twgubiU}vTA{iUfH zu)UiIeQ#Q0C%uYGz9sslT8w+sKjN|VO^{jonmyBm0-jt(y=W{11_rt+0vf`GEn{CN zNqj}B@qJ^RVY@9=_~CV9ur#aT2(3FlsUPELaR}F8B0)`@y^EW2edVS{*7>}6gSVsJ zp9#`!_7SYo&jT1K(K=&r)`{|sjue{$GmwL((|3EWrL6-;cP7Gg_7p=V-O~0O}cCmdjcsr8~YbXv0h;vosLz3W!Diz$s7M*XZQCEghdKu(=c1E)loh zb304vuYiaFG@2vW z7RH&f0Nk`^=<2mT3?Xd7Epl16gahu-$=mjWlAayE+mcN`MM7eEoY0fW_}*Bo4pE_q zhxatT`vG<(eGCbPVN4hx$vp>jkyyE3KLB@n7m17VkeUOIsX%>EAkN7#y?D$MX~RgS zH7cu^r)Q zDuvCt`=)>#vkFi%U)W-QbCe5K?2DpxlZEL^d#hbk^Fw9smq{h*gJt7z62`1E@C-0|t{qK3EX~q3`66KOWa2zb%PID^3=CT z5WRorqWt$&zWuhsaKTY8ulP9&S5q;481^Zsz<=AUKNt)^#166cE+JoA2T^>R&b`k^<6aPm-djdZ~)mmA5-V-`wwc z?;gA3lQ*=Iuu&NwAAY?pNzMDBf*OJX?*UkxMZCcNka$YxBA@C;yl1ju8uFMd>tvuk z9B#F{7Ntk7e4z(4uP`8CF?o+7V17T0L4MXA59gG~;Jrx2`ic-82lUkg?ByhRhcwkX zm1bkdPJFdo?H!}RB8?Je!U3m@I zYfb)48Mw1=HJ%E`x5(epZVP=*jN)&7Z`H^r|8;y9@KFs9PO>b?JEWzp59%F+o+VOj zdhuR`)S}bfq{Y68Y~=`eYj$VATq@Or!7F$`gZyBi(sD^joB)y)8SU(R(;4}kDeC># zZI$*u(-R7}>6HRmD(_jK8VgEBO9x+373cREPX$^Ohs7i zUtf_Eef+BsM#|CRxhyN{Y(vD1slpp&1}~iQ8eR_4@G_qb&?DmHPzV zk$H==aXj~}FNs%d$FF_;jH1SB1YK;D&?Kgle2!L;M=ee0o9K|mOt8P%#N|>b%C>%z z1Ox?5Qwz+_%oH`Y(1h+jL3YBWQZNmix|-k&Y?B7(7WCC$auJmN_gbq&}yzPNkWD-7BfIpcc7*KQwS_bx0_}Y)?yMHdjbHGJsN73U6`9c14L@6ehA*F@1pHTxqjiNU70QG*r2j%&Uy7|rC_8{*}hDMeR4aa-;>jatGa_pd4C;}ot-^W=%;G+ zTwMH1%GLd{!oq>0mc~Yp`H%M3ZB5lm@13&tYzfn;GKC{56~x}J<0wC zFp2ItQYhA^`=^6`l*{uN%jxrH-0n9&YHI~{Bo!qjz8+jZBs)~(GcroYLD$>VrzeGM zj$@;L`}XaglDzsos19D`P^t9i6Yu%poGGQy@`)r}&(Ry`tY_o-A&m@o;jysD*I!Hd zrNRoOy`V!0_1n`D$Pwh>M6*tjg`@x2ar*r;D4% z6wHe>?!|VyXgx2Ys-Ai6k!wNJg)1KlC>Y&O z7vY2M+4?#^rG)A{>sXL(E^8B3gXG8Y#MHGC-^^ujddPbEz87c0MEC%~t9n$-Qc6=P z99BK7z_WTiaXfM|p9@wmcslRMc(A)s;(c8g-a+C5u*(JWwogpu0~TY}+9h+*pGvn6 zX1PyvwZqH$;Y}cx=YDVL_OhCC-{>kx9#rE;R>hS*7Vj90Y zp2b2^!VvgxmHG1Fl1dOf8E(#XvZ_{Iu>JM@+VWPvN@!rJ1+xh*IA8uS|3uEFOXsC2p*WAcu48q|mv0G`y)-fV<%YY1CGJJ&%= zxYdtK&8@9@oI4{A=w^#%!)*a$LervKgzbr0=>j%-b0AYe!I(`m7||@c`e0gQWaXIk zEU7-|G=Xz_(kKZ=BL94uW0EN0#@RMwQ9c#-``yyhud-9lOt{CLfHYYkanwoq~q_N|vyrZcnx`9sU0FykVmf2D*gQJJ7i>;wqbB z!mzsvyvUIajQ-2an;@-X;7)2WK%^EK?T~`&J<02T>DTI+vBB7}UHbo4)jhm_;YTZd;!1 zl@4f_&{t+_Su*`qIMwc8+h|;n%(~Y8cu!j&Ai#EDj7D=G+wPgoiS4X%`~=Ic=M54& zL=hh7&c|f0T$FJW4t9a0FfcH*dT}h!9vqCHtZaiC1lmoh!-8@9oKeg1(e(0tJ$=x3 zOFCz}E2z>rR(4*IaH2p#10D=)g;lr|$?2-fI4%)zWS}jydEt zi2cctB^!~-27G(in49g^Z27^8Da_Uto#j5u_{3Tbeu0M9!#dF8W-Ac6wvduhtoB$| z$=p9rx4UjRrLMTHm4=3<1i3b1+CCT;sH_LtxMW$5+x4%M?I`!l$z0s~8Dg9dq+E*q zwIJ-sSCnheW^+!BCfq6-hhgD1)L9e`=!OfgZ8XBuC1)1_@s-3V_Mq_O_&}sazlXS= z-Ik>#qjdebr7<=k=ucsOAZ-@jQy*-R$v}GqY}A*8#@>+Ns5H3Z^@ov{5i=A2mOfwIx)~U^tbm6gRWD33FpG z9TjB5dfCR`b#v1XjW;vG`p8Yh(%0ZPS#FnTPm>puX~g zJ&hJh`|LCEy&p_d2ZXn}y1SK##Xxqho?J;(Ad}Q)E{f4-bD_6+3t}*o+sI6#svi>m zQ(s4N-lJUa_2Mp~FUIYi{3q>j>pB<%whY^l0-P8$XPL|O4-*t!D_~`&%}fMEV>;JTg(^tkp}=}W?dj3khzw(D!0!0nLz3`nlPF<_BD(^wTiW9h(P$@7;(Li;Ut$G?~^C2o^^1eZfRyK(lFkI4gyKFbm1?e{E_b=*Y7J z8r>-I&pW1*Bcf{A4rKcGa zO~gk!M)8U37u5;4l_eB3KP{RP6WS6h)r(Crf`| zE`2&Ho8ijmHl`V=jOSV3Z|6TN37iuho!*A~|*~rK@>pha9Mc2H{ z7yDYdb|)?tH2K;*>M0=*f~)R{Wn|nrTLbt&@~JV|+WG*7w9l87jEOcn-LK-XOx=<$ zmvxi5Q;z>t&u(8lW&t(fA}6G(i7)s~O2Y6EX#Vvhys1FHzm3dl!pQ`5`oQM3sY!RM z*BN{XW9zzsX6WBaAa=(^H&$>y4j9s*zE5v2hlhcI&y1C+nu% zuRyLcUqzpeymx?2GsDtC&mpSG%VUau6*7JM6KKv=7$-#-d2^YdX4_;vG4scz{&(8BII94QR@RE#Z-Tz#sl2<=Ie z9bf1b9VK%A8Fco2wYg_6;*BAMm~|!Jp;|D{y81NoGFkv`+{ZKTKj-WhCo$I{%mTE= zTiD&$4-B9N#%Ln9UIIUrf_IoCe1qaNQ22cI)9c`;Wiool;Zp>-$8rJK)gNg6wP&KMpa?nSXI zr`Uvb;-MGgZQ-UWhG@IGVn~8e1e~fg4QZm1yCO8fH=mFhtN6F=QekP zf`W(@j9v+Q>*EBF;~z6nu(9=Hpt0;LP31|!I~?etueG%5%s4%Ik?_cSPVDh1?RanB zzQ7%$;CgC=j3tuay{VkoSX|}>S3-#RoKMeIQ zp)0?-dKS5Xc~zG=)AXSbG$zLLFS$p5CfqNb9j}2f?oZGkbQYn~(fm(DsHOBfWEtz{ zp923N;?x=V>T0KJYXtW=FoKjEPtBJ@!@>+NU4l3*tk0I73HGUhl5<3gNvJ=0@QRCozpUS6K+X7>Sl?))fP)#~@}-&a;2K780IKKfKJ zsgb;o_2y*e`5UFLCN>&2gNSuUYx2K{ScNa>Z3e>7OT|(@=DCml^ZHaC5j@o8I+;g| zH%|RB)St*=urXT*5?44H_Wmp^8YCHfDHxTTUj6NVE-Id0{NMSdn2U2I=>3%nJe3ug zp27L>p;FR3OoK{=Z=#lSyI)Fk3goR^vt+uem~Zf{H%YQyLQ*n?Fx>ZaXPvHEj{zvm z?H>X~XlcNJu&h7*!3YHuoK6MyG0#^~~^X=R9se+c7Q)BY``y6Gx!T!K@`LhV9 zSc`%OK5&mqaOG4@{~jw7z|;OwLg3Q}?tw8ps+(<4`e&#UU##`N3K(K?M8m zAmot)PSjH6Mf_ZlRI?#P`QZ7W#fyTBOhCVVCu3+xh<=(vUiKBy$DNBE(xVyCzMw{Z zkn7~ol!VhJH>9(i(g4)Uv-O668u^FXhA1#Y$?iJWJ1ft^?}|o6-@Vv>KoN{Kt({KoY;;?;_ia)1T*AeeRFTgbJFlcRQ2w22D4qu(}1Y3N92s@~2*g`!Wr zZs>+_n15V&$WHuQ#~E;=IrF^l!$ublWXuz0Dl_{;$@#1H3M2BB!_k&ngcB0ZDxF98 z`4&`Xa5Qa84`5oViWpAD6-Rv-K>%uL-|9j|CINTk)k9bfvn5|Xj2q&*^74-;Nnb%i zrCJ$s`}>?L0n1SYc0giW+6lTyE;pTkKzJL3twX3y50b=vC6>mIHaqzF>*<)FfX-b_ zs=YE&w1Q!@JX~NYTJJ(Zg?Q>05kb!J6jUGc@!RyQ-Qcz_1##^CN6Wdsi$yvhY)m`^ zn|s+`R{3oi@oj)-f&h28<=cZautN#)VAz%wA6NEJ5EG#B5BJKdklJ*my2XXFMpz8# z8Vv|pQ!kiXFQyeNm8_QMR99E~c%(lZFtal*{pD72jr03TUs?!BQ*@M~;xMc6n89k< zPIw9Rat4JmfL?;kSI0(|B`k>L6cmC>w$tn|_m_;ywq*M0V<$B@;%y9jYJTlbd6!JS zjC03Ka_8-j)R$51&`xUSu*#~ZiFl`;QNEw7EB6vkD;2m>abgb^Yh$Er)VyCwabzs3 zo@^1ZG*o?09KQv8+9G0iA+6(0oYO_DVtqMKt+_b=wH;8uNB2N_P2F}mub-%llE6N0 zfb_RO=KBI&!Z(+Ofd=ap=a&|>$pax*%SzEK*|7C&SQ=IW62&=zg`XVt(Ccjfr zOiZ<4)FK2x8a;SiyQ=-fA}j~cW)vC*8ZP!Fic?BnlpY4q zATc63w!#5OcfQdmN6C5BP)db(id{9vo#Mc8xJiAg*id@_57A?{5>qzP8kpc&qf5eg1lUp1k#6tldzT-X= z{3EN_s(?kCes^T9P;X+P5P%)Y8kwWZ znAYBcsT6Wh0WQX`Bsy&VYKs~LLoo`>k>yGgLBK}V2H>8hId8x9dLzvveAqRddV#Hl zm6BD9h$9pn20%rVs0kl_yexpBxhFd<_^(6(dttQu1^pJu?;X|i7`3A!zBxN0#7_46 zD@MMs8VpRJJBB~qS?q83yx7lbu_V`TC4EsDMr(+Tqql}p^RbE5X3I>@Q@|rtO?ys~q*nl|kuZDzoz% ztE*U~@>Mi^fXg&0xhg5j*~^lgmsI5dbWsUr z%JFJWJC|wM4ZT=Q(UFS%1$tWmlZ}LA^foXVOJ;mRwCQDHg}7NH-AkXsben7=fI9Q& z(vWKC^YTO_*zh?o12pPBtX`Z}A}r|+)NHP!YFS~dBl67)uA@!KS!!X|7lsSs5*H*? zd*k0QYqb!|YDd_vJnT0$1JfS3*hv1{*+Aw4lSp9CrRn}@?hWFjNe3@h5gd*?3BXFu z?SUGW78jA4JIAXh$4otIbgT0P;S-BO$D-UvudS`EjjVhS<=UZd-E2)#(@K$WW<*BO z&)|;tQvTBH9p-aG<-0#{`q>#DH5O(IRZOm2Yva`a;O$1G1n^1~y96TNho0G5Emob8I-&1cJNf>XyIF^?(=p+v||cSS3$A&a!?<-Ejs^ zPR`s<2ss3nL-i$^!ixE%3PeVnd&%4l02lVkw8uqp*kCJ-bs#+hWv5t)i({S!xy4Hn zL7123T=2PQclw;wxNU|~P9&*l%YejJ4>FC|_KDqqO^J&(f=Y;y0S8Vrd~_-KD8qH> zUy<;151UBmi6mucFCk21UEcF~y(<5yDOVLF>bQ9K=;W#At;9zD*Hes8T8NI75U68F z15gSDM2=WRtbB`j0;`F3+o!<l@5r`p!(4TRZG!SrC$QD(U? z7=a0VPo1SwM44Wqpa#8hb|y6-f&d3aODD{n^Si8T=PPGbVpl$wW1>_lmHbYU8-i0= zz_FcYv;@UWM(pDAWN4la{e^2?{O_DS3N`ynWG_A>rX>Gd0D$r-LjYsHt?%!k1TaW2 zC49wxawMm1ukF!lVU%dFepJ>;Tz~xd&U@^Tp7iJCY1W+;^Vm0 zvL$Wu)Hz9;m#%Kxv}PDJrMcd9&5Ob@GxuG3-*4`$b6p&d;tgI(Il#^bj>62d%GOrGM-6ut<{rDKo2bLyEN7>L=|lh z=@;x8G=;GJLcw4{eTeL6c${0Z+vclxzrL@Ky-rq!#hEhLC3Wn8F3BYi2@uh78-4}4 z#6E~>06X7z#Yt0+rFgwS0sUz~OPiFyKQ-4Y2``mOEp4x4gX(z5wqEJDrPC{E*QcN? z(|-kW-Idv{yqKEPwc{?svMk7g%LQ%svZSOgh_X}j;=ivuSuEe=Hd>dZ?SYv7PBFp< zBzTrFV5WF7+^M=;Z^4AIGjP^%ci|i8H@jncCU=QW_saENk&!0$s_a!r^=L-6EnYgm=okxL`4Tp>A~+*0;9_t088JxN;ta?dG zdCi{g$otq1h*_0R?F_ruQAOB@odkFkX1SXoMhfx#u0pjmCmuF2B0nbWbWY|TNW;Z- z#6RFt;|G!r#W_ggfAmV5RL7=F#*cGxto~H})kco_i>zj@t46dk7QhDWe4Y3_M(Au# z_+rN?ej`kg)HnhqhDDqVQ1oHR5YzL}Vo3}HJzp>H^hk?7$$M#g$CzcW=A));`ri!v z2SKOck^EReB$@u5oj7fDZ_BA~87`Yc2S8{57ghj4QzG&D3Z(kR8H zXBuB$@!OkMf8#T!GVI3GfLCS-k9sIMQ^v(CjXg zww`(IpLY``Hu5^o@ar3uO*NgrJsAM`l{cL9(w#%e&WFSS!l$>ykdL2)QzIiAuunos z#7POgP0wT2-Ag)4wrRBj!Y^ND*Vb~^eHY9ofc&@v^tHi&6g-%jRA@#`<@}46nId__ z3M@=&)j%Yv&8ylS&qu#&`aO18#qc5J`8P5{zWRPmwOCdlApLmyyCb*OO-zAJe>Lqt z&x+i~=%E*@FnTU8$58)!>jkF;uqd|* z13&oNMFlC}iub(s(LX706Drky%|GYw*gA<$^TrET|0f`<|92#d|7(!9|L$kQwNBB2 z&ICD$ANGJL^5r0D{L`mTqwPSbgN`E2)j-T#u(`SUA^n~LFOR*c{eB}6gTiYPHltkN zVk0kl>j|T8VNEbVE5)Ou$HhvC*cyKAw{s^G$6w$UGTVa~a)?K$2 zdy;(f4Ex)ZR{Fc+2i;&KsiNcMS@wIc$bZXMZQ*ZPM}NSJ7cX%0Zu@@yIzmH~d(Xu| zxS_Sx{1T7naW;qmSTUShS^(dxP=gBEpx0DSK3p*V&JYBMrmz!dXLQgBA#Ax{-{BEB zg2iV_$#TTV6Ty<-E$C_DaG<^*)lbZ|v6K@-2C-Kx>;Ro(jV|IpYWeExOL=^U=4H@C zwMc)u5`Vn}rLw~+NU|Oje9W(|=Cj@9##gA|-ya5pGZqWwWb&Yy1{em$I z_ue!`wjVWGN)BNq^)cQLS1E_(`pjANdgSwGm2IW1@G#;5sxUT`XQ&Ud5MDO$ z58FpiaqIs1%u}G6kP)!I1`{IkA9A;6fe`RkPi60uJBr0UE2SHFB&-I7B>R%es4_$H zC6FdE7hQG{f&Gk_ZF%c3UTU4gqSw=40pezWTTFZenXzHPD>Mq=yp>V0Sm_iQfWduS z2E>Ct`-HGp-|)vA#$k_wxuz6J*w^j~i>|8LE|=}d7!tKathy=84bU~OqFlDf?^Z59n5@B1 z17%}I1K#Nwt4>(Oi{v{iC7|4vO;$#+gYwO^xL^r|#Db9SP;Qd>xCTDk`{%)NcR!(nI^PZG$kb*&y4700_Mr~h<9SEUpY?{h>20rR(?l0qV0j^`s^I|yQs|YW0;B>F!8xYF!8TTOdiSnOp09FRx}Yx0L?`OKtou03R}AWJ9I);9*>}f_SPZ8X6ejHHE%m3!wr|PM z`iX0hlF^&FLzBJY~^?Xa3me_5F?Z+kEwcg1pjS}5PF@$#cpXB;JBjkB=og44jY zQ=6uz8_UZ>4^eu%7iZBUUjIH^Rn`p6AD})})^pq_oaL~Yr8ZC3s@3zPyb2SaOx=BR zl^h$LCgMRZlKvUwTo+xMg#Zz2yA3pIZB!j(4)`Tza8#;$o=J8@ZIsi&%&id!SxR zz&g>vPl=5_X+M)4}E-2R=T z6Ei&905hMS!mWNLB^a!f7P0PkHIE34s!F;Xh1;&=*Wi;c$h2Offxb8Zs1CrhMrsFS z(iGjj@<7LAwl-q&CL{Ge?_UHT>rhFu`2s?YDjJkGmH0&RP{KObYTO2#?DqGw<2FZ)Uhkdw@P zUGw}cD<-Y<AyHMb6`1}= z;uj_R8R@9#g3+&2%5L$5j=?O(D>it{3CO8r`jG|B;**IubuL~WUso2~XT~@1sB!ck z%3#p5R1-ckYA7Bn(^so>a`_`p9QfBhGMfe%Wmk5z z1DGD!J{oAEAWuDR0KQV5UfdQ}WIK_tr5@1ri=c9eYsm(kfUq5fSg0a4= z?AvBaC59(1j+wVt9iVOU-5ae4|AD1=_jbW1&UZ7r;2R@_Z#8!Ka@Li>v3q&3^Og^^ z;yDmR!>Hgasfoj~-#P|MH;pZdx^mKG4+*`J9-)0?r^Yy)w zL!QfM05~B5;o)SRESDiSe`x#-?T}g!!2JNQlN=wPGZi&;am3J9;N7=R>`aVbHZe6- zLKlmvFo28EioqLNT0Vcn&o9NHfYxr-*L?qr3Jm|3B<&=xO{hWtDh<+rF#d)H3U=lopii$|om}qQL?=IW>zd2ViDkm!_ zx^t9Z3>DLw23d)2^sIH7b4F8?N}5Q*!nrqW{}} z#4`c`?4;DocI8x5EAE7Zghdhm)D_T)`EsWQxy}6eGkTD2|4qXXe)cT$KkzX2zx_pK z9QmA_d9<{&UdTG>*vWnWP9-BI_S)3}Gcu@Lml5wT1cU6UO!E)YX>1e<2-+2#V*ARV zVA%XDJu3M?OIy1HvjTCV32?9~A}2@W8deTprJD#wRQQ+VA31-*T~z4+G;vb#FY00n zVKt=7b>?<|A}!iGlX^}i_=0_lhDsh(58dRz|^0S9081*Q5GfJ*4}(j zEiv##kaXGHbGUeI)3^dvA5fcKwr!(77X3sZ!IvLEJ^9c_dbNwIoVMF$Co-MXCGIC1 zkz0>qE0#f8JAKYq^syR9x#l#d7v7K7?{2GLc%=i5Ln&}_C)H|fY$PD}zW7^@@GPCV zh z52vA~cH36E>rg&Fw!X13dJISq1xgm9ESsyS$O_O~)Rck6@PgUys_GD8w`VUL`jNc+mH8VPcpj5oIa-OA2U@90B!`p zL-5n2n`KtWSbFB_$?Y=%XKDygHBz;`?e3KEV=Z#4I+n=5{PjToo@6@*oL9MMJK@w9 z$$OFHUzj8^+z$hXNRF4+3B?*HUd?h}bm~MA0oY;_!HG$Ix$-$a{$6>D=_MR4C@jbR z8(VvP`)(g5-s6_8?neX4wyUJcJKXKxG*H}hLqOO11}c6_O2SpUGczh>s)YMH@)X&2 zu4nx==s;zmfU<^$H~4P7@wjAg8o)flr`qM*d;r*>R2Nv}^8E48_l-}{zJ(J3`a);9 zOXx+GW0UFY#`UDDb$M6~dP^zdgKD#3qZNQg?pxPa$-2yDFFD3e)K+4_i>!$2-k?QotZV!*WBGs|vguzfkklng}6^gAn$j6>=U*$jey zBK83n#*7n`Y!+B*qQaGo(!*>N`Vq`(sFk1h-&erl> z^<);qt{#q+>`$azW1Beq^CBNT8qSmN z@w!a{amH$cINdjAMsT7Bx9nFPtjdG)6P03AME8X84KKY+LPoxw2j?Z{f^=$O!gl-P zjof9BfQ_=gXw${5|8u7=rT=(Ew=CSun{*@9d|~8aPYxX76vgje@^%w6y!n6Hd+(sA zy6#IDkc=b&K@ey~L_sA>hDHPgB`7(AfFe2PoFsz^f@DN;2FV#D=bUrSvB@-ZTGYq) z{mol7Q}a#D)K>%lQBB`_Pq=6AwbxqPrd}`E@hq!ySBBRwIl_Y_*^vSM2<2s;8m>#BgCs3iCpMwD?Xbg-R*786vtp-vn zo6eP4e@3&olm{kdJLqd;hfvbnmes&~GC%40=V3ci)#WP7-+V~v%FDbTH$a3y8ow1J zD8uU`0%a*Xqx=0O&%?8gm&HQrh*7yWMq4DgVEx_flMXi71KDa#o&1NYhaBvj?3SWJ zx@>IEa63T{+qzd2Y`Hj$Wy4`u?llq|ONM#$Jpl9G^u7|~07px((AR7KCPqCX!SS}d zI_(8;_MJF(mrZTl>5~#r(jvfIDzh}=w?Lim0U|FRxF?YiRoT?8drjTc^-;MSUjV-U zP(i*v@n2vd=xG>-C{!qt>ZI$6buHS3x!dz7dwt`xL)^!<+XNg$V(DnlX~*2mWcrw@2GM_)Uh=!UD$HZIwWRXQ|7 z6K2O&riMF4ic1Fkvnys<)`qqSIOb`G>%Kg=*=Z$5iy5jW{O%Gn!qzC8BBTk*MSf33 z{*vHtd`k5m&SSKDS(cXLJ9H(tEV1Px=@O^{zl*#ZEVH6m>n&WnzK2;sPyIVzgrr|i zsh1xc=Car^g2^QvtvQ?i2-+sg59|Ll_|*=F!fH@S3&U-xG01EWbw5-UOkwD9DYNzi zMiZ+f!Re3w7rI8u#d!xj8~dalTJ@q&LE*MEI})gI=;k^&G;D;NTeGXaVKrdvugvkX zFUvdO|@0}A{Ex{F#gz47DK z3f{JglgM0;nk=r7!LP#4c>pdn&ToJ1yvFIU?ADVo80NTRU!d{VO<$Lb^5pjyW;bYQ zjial)^HGI7R)iF~*#+0>IHE@MRr&*dqNpWlmbWPW8@zUmpY< zxbJ|VPiV&x?TnpF!`pv?%}FTP_|^qS8H4 zUiGDql=OThB#bvhCTm5g%sdVB)~KlLmTz%Xh_(6Ye~~l#iO5;C19_5~%hr_>!#vBB z#%-6=j-hN(+6dV*7)PIBcl@mcNc;>hP~e5IzmO=bJlNF2fUO;hCc^i+j8>Q8&S%t% zgH)(wO}MD|Uwh)9kYC^L95X46b(jIW*J}@Se0bZiCA%abPGTHBRS{Bj4_6P1t&Z2u2#C!z@a;u# z8H?FwG(3C>YszZ)IXOW`bS0a|F;i8@I0VqX1*lE#7bIbc|fqT{qP^Fj>M&etW6%y`8e(AIKH+M+DUT)khG0GJMVO6B%Pc%;^%)G08y0 z|5l46#FdtoKEh1x$;krt1TG8&K))wq$~ERZr01x(#$lMup*iLv8~tZyhm%qC=Ld75 zuJv$z?I{XaM!7xA1(FT+qk$tv3RGgeB1}|NH-T?M3nqT9(Q1EDhlDSwL)ZWYTLfUj z?USlp*5C9b0#>lLK|>jOn*WW&AOHoN!GY`n`0tEVXe=tti3EZMR5Y0mhnwm{FDbnW zpJu`t6<>)D#V_bQzfeCYn{-}#i13ww@f>OPxsZ~Qj^xhJJZY2la-a^2p&Or*{sSpu zUdC)DLf-@-jEr5e&b!qJOkr)jwBVt7#c*Sw!%z_a;_wC)dVebZU?48WOb3U0F|B|2 z@Qt^zD}IcCw4v@egT}Km|0zqW-g|-Sb?w4f6aBbf#+rvZ160&qN#}`V<$}}nnq(X07^At!zGJz!F^I+-+qw$g+b(f6+>0^*EkAO@> zX>yf!t-rJY#y^c@6Ob0?VhlX$tQkEG`jWF?qb?T@H(dbGd2swT?u-3Ha*lWFLE)ph z2~GzqDUY@4-f!}rpHLbajTIL7QU?`Jkv_~n*ccrXkmPDWaAiORu^8|=O6S&`E&07X zD-R~e(A*Hj80ADZmD1NqR%1>!2$E8Hmr3<;BjAU;b;9TER_ki|L{~oPeXub$_I|Eu ztX*jeP#(&Mcj#i5$K|tB-^DrZ`tqb~npCS-KH%aSiBV{Z1TalfzhvHIC4Mdl*ymw4 zWPbbf%nDr=h*p50o^Qf={lojLvCRpiX|4CABBm=TyR2{8b3qP}2THs8K(CVroSQ}( z!UL@94YE7ibFX(qsZ3qLM}ULOd@%s&?}ia=MDIob2bBR2`494|jna zQU+>!n#ExRs?rCCpWs>m&Bz0Y(Fn#?V8INHi1Pawo1!gnmwDQ8z&~ZUZKGPozO3@7 zZVyoBEo^eIQLyQ*V8G3$i0CA9ffS)*;}m4WW9f1X8wgb9(UQx>VKfekD5X=td68+t z`C+wG$zD3C@DSI*$lEN*3GTt9=L)B$XBdxlmYK!?>aiY`Q(XkOMIGMw)LPXS@F*t$ zaaqfWBoE|hOs!cY|1OuTA6*_@4};k-Q!z9F_*J6r3c6{Qq?|i!78YA=j1FMq;A~Vm zY+6@S130Jr(fQ=ov=E}My`D^QVI+FfF4SOKzfb0b+U2Mgq}H-goLcS%&kTG%#4fwA zENGt&HH4S!oSqbKRYz$pX&f;+%)y2Xd$#=7Gc7ug+j1f76);b=KsW#_#(>wFYW;a* zl(BVFnxS4u?2qcx&N1r-xf==E7gHA6#h56^$7>tSi9yy)4kqKAZEY$>O>w5P@*Xw5 zE&?sU#h}-e1}#!XlCJ|mu`wR@NRrg-t#_p&=i#Rj+QEhPfEA_ybY&A|Rt0w@qU@bI ze~WD0C^aq@*aFz8(>lPP=P@o(Bv8>fJP|M)HU&0H#SWXLW8}%n${y%x*~=!?7jMo_ z@pC=!xqcW|t%wYDE9iA6pHn;S6Y_w9`~ zk6ymM%dlQ)wl(Uvz_9+EwezUW$v`pCdmh1JLttE4N(GH~rQprRomCBdwfKOiaAaDc z#(UD+o!5r$2B`MzhR<+e7F_~U44970R_P9_Q`ts5r1vac42H4_)(VVGg}m83-|c)A z{v0{leXun#2KZA^LvLSb{bG~);QtKPGm?R4#NyfhCx^N$x-&5`vCVPD$|H9QcZ+c> zy5WgZn~{)P?^U7%meBNuLl%*iTWo-4(TUoATk!c-cdf)Y-|lkagsmgrJB_2Y90Z#v z!2wu-5E%_bOaUE{R1K>X-#Y{gNa0`zFc%R;_)H2oWg$dbR(9F@+DaAw7nTO+2=+F< z7HU9ZDFQ$m&*l-)I6H6xX5AktVM4^u}J{U`VZ&bB=&4_e!@Uru}zBT~tG?PWAtF#@}z_pAft zZQfT_&Q90MbF6@*rwSnpx;Quc9AlA@^zBoFq^ zjv2)Ze$8pELD2buBU-HXiJKlMYK(&RvlDmT!peAeo9m4y1-t!cVOoD)l-7EeQ|k_< zw!eA6lq@g>*qnP95HO%#<+Rk)V~+-WuO_}3g+%qhnR;~GN&Vm&P!(ApR52g#zRf;e zxN=ipQR;N{`4BW)CM8>?Nkd479*iJOye6|S5OZ^GGNod!oo7{ z{}y&l>AXLgbV47;8UMWfA3kpaR1m_-%KCBHH zhwCCzW`|0{kxP!0R@41VX1jm!q9Yitx;*Ft{ojC>6LfNw#1kn;O0$U}lPP2{^*-Mf zo=MR*%AdFVqvUc)2?JA)UHL3c&X90;kcwixXD>O&gqcYQ&m{)|f_|C!>)*(N)cnw7 zJSe%>jyhX%6j3UMc+t9%{ggdaq-(Q*w+3pKdg1t51#u9*n@C_3^V56${Q96M>x` z^$u96#cTk_Er2m=mV*$-D46dcwYRi&Y-KqyxCoGq!yGEI)yhYKL{4#~RFZ3f_!P*5 zFJ3)jU}ysh*lF>@!~0BDtFnD-1;AqUB76c7D@X?*$iqkqSF<={Gd&P`XI#lpy&Ix&PdC%D3o*$cx0%y0(1MU>^4E^ttQI9(| zia~vBw1Z>H5b*FYDVKnKRe6kQ9Tp&oK9iZVR6%u{az5?w^Ya_$8n87U$xn)L+-=!B z1m|t}6vG%Xs{P@n5$K#e^;Hk3-VvH=lFdi&X@Yrsshsv-zLg%4e@W0?rT;C%X2Bij zQLYd^-}n6b^M$E!Mh#<=N*M4R9_j-2eoNJ74xo-#x!1|_v&_e&2_0^dG8%4vr9XsK z&WrXdp^DePTxcD>Y?!jTirs296S{~aWx}#gVsmAeeMnVXpL5D~E^u=ckmA=+pxw|( zdBJ$Lei8*$EnqTM=>Mp!xlKw?vpM8e^7L%EFc0SPSy?b1CHF8;B`-N}UH^AuyY;76 z@zY=UxcoAh;l8}S1c1$Dbz8cDLcf&mtajWLAekee5WE^IVu0|70Mlc30CZg=k^meJ zr*g}whL2_guQO7}{=V`U4r7HTLVu4#c)DH?tOm5o2t_l;_f%jo=@kUgS-$TGj2$;X zWO*wi3(XDmQyBvaP9sF67%&(|fV#g>II$X7C~*;B^CQA@U^_APE+uAh^Spsn<2$e~ zgIK~5)^2<)3iu7cdGM!h+@Y=-SpAimes%JoaznT(j_AQXW6##63azUje_?yq`vZvF zH{|f$>lWCLV8y`fZVV{wcf(x?I>i*l*>2LdUKYg(N-uY?|7~0phH%CmXSWFQm9(_9 z><`%w1q7xL%4<-!jyhEfT>2!*-GZ?G%2Fwwyp73bINZ%1!R{%lB{+s~d3#bLr=Ye# z-X8fpK2D!lDw?y$xOCwZosla=;}vazcR8Dgz^z`O92)B`VS&0HVO5r zjiRyj^@1)uTr}Wq2>gR<+0WP&pnNyMDl|VuC>660wH8{#9$h^LmXT&eUk7+tfE3t9 zApd1ZDUj$-@J%|OZU8kX(z|=TUqCjREsDJ4O<`>M$+K{!8a%+7J$ias7~YjKYuKbL zJL)ny@vBI-495sLxt#FxUwP>CWw5ta4m?{G^9=Kbj%95@9ck0I@-8y9%h}E#pj37f z8J(At*XyTdn8g7PA#mO+#HTFlW3Hvy=B|*f>UNu6H_|VO1+w?{fC4(3e#fp=PcY$uvJ)nWo(oB*z!cZZw(RdYb=RQ{_pAC|r0d$iN@SQ3^s+ zLT(Acf_H)pe1B52}oF&YeJn&DcU7Z(9a!w4{z z81I8?+P)AI^QCx9w0}=;(=E0rgD@-1o%jhDJ{eE}>az_D$M+_Ahv5|fo^=RUyH{s= z_bv|Tusj9o*{iXO3~Zt5UZS{$xJx~Sp|~i((%yy-SN{jYgV2fAs0WTKN#SMr2%k`f z?CC_RPY9g~sP3vP40yDnyMeyu74%>W^ z-_|Cs=;-;KM8e<-n3WIQjK&$EY)?$YEbN zOk$%_GiI3eBi69s>6+P@zqtSc@SR}knpDH%ZopG6lM8s$8NC9`@mOElyLgeHQB34) zYRo`0Fe|}}Y_po=bHBq^3t&>mc0-Bb+B@ng2}a^i2|s=4s_a8A4=$i_{8>m*CVThO zbopoCBICtwtiiB)2e^E4V1~z5WO!GRx9hiK5P4ThNx#8}c|HeY$FdWh6tDhJN`IKv zEoX0TF`~UZflhvRf%Pz6tl;|RknIZvx%5`6Jp5k)wCp3q>p@oixj4e{Zdtmj-ow4m zVA%iexc<=#AgOnnw4K8}Bo^S~n^=n#Cmr-u?GWrPM`bfsXIvg7-{2NE6c{cU61aVfiiacK+p*g^h-dVozc8z$6d22F{m<>OB}UH~N5;tD~pm zyng8k1(o~BI0jryBx(Cc+CYVehfv}5r|ZEmOPJ&o^?<9hr@d18E4Pu9MV#g50Ij?) zmGWRoG#(AwGteDF`(9oicj3(*pT!&I}=ZNJopGg=PwzS;m>69>|yBvUi zg3HgRi|}Bg@?BXQTvXE1avLfH*#nLqr?f{un>~vDK~wX<&-9^R^YYUt#{LijRp0|% z$?r$57U5gVelj;V`!hbX{`H0QCT$RceLTZ|Jh=_LT_=g8fg`s)vVLPzgWOL_+xqln zFhw#oCT+KvjK5p_ zkQJoi>4Lt#zMCmU-@e^BWc`O~cLEGb@8T%r9pwJDzya*!z@LKjmOA#LQL~xLMUL1R z1E7MZ83eT&66{2{VP1x@(zuA#2W6M#k@WrNj}t%t_QOMt;Pmfh@Q~IYq_4Rg0bf^x%ef*XcLERDuzC+~}0kRYR%AMD@G6VPC5#A$$Me*Ui#5k$#-ecg~0$+jR@6b7o{nlFie7s-9JE?>^} z|Eri8!d&*>{5LO#CS#y-axfz#7h=Mvgl+A4Pzl^6C9}ckUh`I^sE&mGfSWrHnTg)N zvJhuAI--xFV<0i2VtXIWY9Z1iO)OKR(`#~WvsJY|p)I`aF z#ewNfh8jo>L^H$ejVQrzsW!&!>}+?L1cgddr-_&D*vK|)?Pb9mi6acu`QpC0V`*0u z;&>#qd()=K=-^jHf{n+$#(+WL_Ua4bC&}=p!MqNyi{m9~$BPLYz2V$jt)CgflMc-f zXZgx)=6aK!>kX8_tfo$7b?yM4JCC3k?%KY-K65^>K({ToIrlflsEtO83eU4Ru82*~7*nSPzAX8RXkrRWev4aZwWbVueAGq-UwBY9-cx94a+sSig%^ z071J4{dtw3YnlX_1i1qc3wq4vm?(TIUZ2!z_>O3_%aqEn?AO%OGYH=lz|B516$eTWaO zJb}q^tvsOSawMaZij`fGPM5vkpRPthD;`>6OwgRVIpw?;X#VWQGbr#u(DD0l_CO}! z$NGMn%<4I)83ux;K;OudO4MU+2T?b{&rf%Nr_EQ@5%Ut(+`FKQY|0sE?&WoKWE;8- zYtTrMV(4Nip7aAnFXTe~fw~Pi{1HXory(}|@nQL~*IJizfrMr*?<(jjSR?P>mIlc+ zT9Y@ND#CS6I_nQxIx4XpNZ4?ia!T*3vg^Bhliw0^tjI*TqeeN^QiXQh!UW&yQ7_@= z(!<`7a0bxH0FJ9M^|2&JLxAMo{La|H#_)bySIJ6p)XU*SrmtPG4MxRN*^_gvVGNiW z)skpvD`&0!iGWNSZyrYfE|dRaT_&2>40Nled@3!CBb)Y$1fcf{3p!=pi~{H1Rv|W7 zJTCB?o2l}Y6I-RCF`vo_kr}Mw$XU>eLmvg|4;VmbX>atrO*ET%auu|ue_v9fNPPd* zQ#8m*Zt<2WDfwgl`Se5B>YW7LrA|uFLab3)539h_yc*tM46?|RW7bZZ`>I&j?Md^ zLv^hYLGCLXO4Jj4GR*2%Nv7~|u1w{^SAo#0H`&PGaCpg0>R`ZuBn2Iu8;P_V(=9qv zC5uDvC$=wje#sIcy+hm*=46MDh2n6yAqe!`FVkMpTE)&*p2QRn-1`w4lGgf6gjl{J z|7vaRoJ`=gVSSwkFMjX(Ah72VyIzlr1hrFGG^6vPq548T#=Njz@`&4VefWlr&06y6 zx^W=X(D1<YAAXvxb+e z$x*UjGBXK7@OE}}pEKL(T?oiM?hzQt#`BU*ZBvGL1!~**Y z#(3vS^m__b6>HxX1mg+sbQ;ctI+q6t=&^FYOWe*VtTfXi`1S)l;{ zr!?lgNS89q^t)0z!J7=Xzn1dd%L62D z;VD4~UcPD`qe3<>x6O+CM`ve!`_?_ySRr7>m_vaYM9JrehO?L;-!ZE1DmKz!v5b&eaqQ{4eIx%^xS@zZ!W18ShR5#q2nWNn{cKr!gI~z(R>6`sug(D%F zg_~b+QRG*3I53~t-LM-jwV?C9dczNBf?uB<`KC(eea*6NsQYvL zP%zu#xup0j8l2vi@cY~<#fE}_Yx45svr7N?F^yRC{I5HEB}4bZFDX*95%^<_>IxqA&X7vEc>4UG^*&Foi`yjLJtBOb#-gsPMG-7|;B z0Cl!E$sdzSKoE_C|JOl8Y+7`A=hq|E1gHB~WXRZ8ci5UfA0OvaL9^isc&VAjy|e;t zPsRc%>U)ZecnUEUsjlHAzlcrcJ_mZSS1NkqBY?{S;pfLwE4OOO(rr!6;?Z@kP)+(A zrPH#eBU#x#0a(h<9-4=Ja2wLl@X`tnltvb$igP?8wB8u?nyD9^PL49Mekk%-L3!{x za6Lq!5#bN=BcV@aM|aUEaix8JJ<)ujmA>NWgWp8OQ71KUd zT#SztNT>HploX5NH>12(dhyTzbzB1X<1S0tZU3AF){VgpszT04{NHCvG~?x33VWkK zaI`m#+@CE+*O8}tE7y`=H|K04KTvl?r9aHEw9au=_6`cYf;`V$vk-SD!)Hc?F^^XH zaC@BRN||M#!F8d3Q2RyrdqaWIbP0-~33HbtxPw(3Ql*pLdXi$HkYv|nrCf>{6(C_X zh*CQvU5uR%On7AsrOphBTW{Dcge}}#S@XAtQIv=<>-1}_eb#tcVT)x;x+h}(Du1+h zKh|LjH1D48$m-`Sph4M%-xv^U%{*UaFr2EwMTR3U1I`fk;~^ZB-AKkk$L!4&&@A|y zEDfZHoD@gqR|FN3h;kMD7Llu3qav5yP?VJo9h+-~@AdA4uJBZEjinez&?y|o1( zDCyyPp$(NB>RvNIDQ=NFe+&r1vEeK9O+l103Kt7DB=oYc`mwVpf3JNe@e*eJv^I_juV(XrHoEK;nnQnFc0^NPO zyXMu%SPy{#ku1%v6Ytl%1?5dm)J{G`goL)cX_}Ha`7qMoo6&8Y=<>C9l23y<%niZe zQHB=T@ljA_iAc_OwyvO|iN>Kah+e+-@5NsZPb9yG>%(kE4cm^TIKd*mFb9^aWNgjs zS@ctJoKhi|!Dt9RDjE1SWY-1}sp@gn_M${o|9%EJyM9SB@51S)MXXnsgTCu~xuufZ zA)5aFUiU9UKH_*+ZM4fTkVJgJpOXg(4G|n`GGlsrd#itvdRg=>^!<8Fw#(-j#rb2v z!{R#d`0OYA^l8o1l)!T<9fIa-)Ed7}E%3iI(jgu-po^+>s`RbGu>{Ak_u%Iw-No8T zEmzY`9I1DO0|OZg?HcNnCr@tf)s|&odblPEP{{{fJ{98Sl?oNaL($UGO0Q>(aj_ui z9_wJmjEJNWMK#~w`h<}P0`bd7;COKvi_N_z$ZhyN-e4eW@_sEcIxvclt|$NbhoE04 z5a$h4L$iP0<;13$mm}2J@;@;`Khp;m%;DL8g2Q{$ikAsO;3vVF>;J5JD00)U7(N=T z{Sw89KhMhfE1aCu`9JmuopIIO@^*iX60vDA)O7okQo7>Pr>JM==LP#zrLfuDTqa`r zACM3gIPat16F?jfrMj+3P-2Y>Jg7^oH^>oldj4{80l%#$H}mG7kKJ=cu`4xcA#_6- z*0gK=eMo`qW`qX3`3T+C--$Vr{(c+YgSzq3)<8nuyF+93l`2PSv1ep04cixP(PtuLoq zd`Sbo5O#`5`~&~X(m&e(JKwd+M@{;wJoWvrcJDX_-fEu~#w=Y(G*)l`uG&>rsD5Xe*k;hU3;T<0d%W=!oa2 z@YfY@Uhh^oU&sLUdi>a$)RC=)QzXqdpP{*iyU_l0=W+v42;VC(!8eUVdH-1s@il>@ z-N?`%8(H($^KN=bm+9Gt(BJVry+zY+;5+yb^c6)oc9na`tzp$sq@~ zOu}g&Xwk>rjz5zQoxH~BatrUu-G`!Q0$9tF`FMVI4l`|WHZ)wv^!#5gP+P~>qt@s` z!`*Ab{o|fCs63x(qQuylkG9#fA_zIff7VR@U9;BRz&yE;;xSouI8UjUr{}!A(Ca_j z4Xi6T9n;$Z<%(i0it9ntK942zQ#ICkh<#5l3a~xOAJ==nFJI(VP{%^C+D!Gih~m0R z=n^C9#gb$?v`tD#;XTQUG85?XAfl})>bTg{`E$iRFeItx7VEl z@{`V)?4OCk&Kf1E=LUjZezX)Y-*b6>KVWqXFGhLrRkef5gnYZRHl@hnmaA3ux^Vxo zzn*#uODx3%2LGh6+?Uj4|A@j}2E)@iPVpn#1c$aVTSsXO+v_{h)dJ)<0!J9C>tgFu zq^rJDqfbm+JuiR!vbv2yQ1i?~t-IQSC?KYQye)Y$c%^M#Yk5~kPcqttj zsCcU!8x`-B4glt@GMmqpo7YMkX;aLvwRA zPuelfk>@mBXRM&8B9X$Yz`=(ZCr#Qc&nuYmuHK|tpG0}fs9GmFwO-@HZ^UE{bx^JP z=)*tvxn``5=x|~H^E5cR?nL8Id&W~xudl>D789jcQ2k<8hSPHPYGb>4R#mtutNx=VDiJ7`$j_ncD4(0 z(sCh*v=re2fkba#o3mp!6GQBp720UPUO%Ju{_~8S3N@=fuF*Tx%ns8Jq=b@BU%6r%KF6+1$%O6Z@=CAA1`uQX0=kGC@J*e}*2b$*2Lt}xGcnZ7 zIG9md!LC}F`_aHi_4bK<=mA5J=*jm+8x=f=E&S9rUd6)FM23_wMVra2M|9*mYVyN# zVgu)K4_XjgmK29)P&N43eHDS)HDR~`iE^4Gyu-Pn?|VF@%hTzeZVpw!>ERU%GI;c! zM@^V_$^aKhbTzN6|c4z1eNz=-3gCYA;=3Ij9EEd{BzYZ3rC=p)79$Wak~ zhzHlKe{6VqUb0qSRcyrfU0)hkP4ppj-%Bww`hXz)G+a34!(K>8L_nk*{ROS{VFD!1W}+U`dNJCe4Srp2mC;RV)a1Z*H}wsQ_|s)CPRjG* z4CX>3SxQEUo1UaoSDSbYT;%*c>KpfKJFn2?8ci`<8mN(mwNB1sqb#%?X?4`)aZ*@d zX@GW?68Z}AG6Ck=dAW8^q3=qyBVxu^69B+KAH=?!uTP!*i}w1J-s2hb!oo`T;HVWh&SxDP8caBK$Dzqw9wv z!Gka+b4?85ogRg94q|3I;?S%v3{fRv%hX;Nf2`4vbTyF&EMk<}v8DM~7j8{WKdOnsidA;iO;<2!xIQ!i>n zT}BGdEzkK31q;W#+$_j;?{h}Az8Dw6ZW(vb()}i3v&zM-+v;^QYjlRQG) z4`F`?#!ATOZZ|Pe%rTuVkdQeT$rf=N%!_*V^!~&tt|+DxeSZF47enL#?Cjf^Zn zDQA}{nIq`5P4e%0cO0FZW>!|dY-S4r5C0v0 z6mBvmOwNR`0=&O2@(vMtJ;uI4s7WwQjq~RqjE7cTXTm@(4Tgp@rU?(nYZX1fgBqHr*zBO3U^)siX_vWS>g3j7t}59tsgU~ig%LE#)3O?@TG#E zlq5zoqFcRlJz>wN>*~UvoSf8eg7Dqe9x*0%(dl(;wUcRMLz#EB%Q-=(xJ-PK5baU=Ah zioBOAB7R>Qm%#bx0gQs03PnI}psmT|+yz!}u-Ct=U@T-l3*oA}baWX()d5$ZL7qK( zCf(H{6yD~`z3_v1U;RSZkJv3dJRBQ|12HSXMe>*v$4qPT{q7tQi_+98;qyjtVA`swxkApIRa6a_1j~3 z6wr|E$b?+YDqRiO1P3v7h*8}=JmgSjJpP=ZAS*AW)vV}^Q^+}aJJ@Lpo0@p$T9Fg* zu^sd(v?oxku+1~b%dK*Ypu*~w@c=|@P)$nLEt+(s{O6&N(DLQtAsBA^5PV8*Ptd)l zpZ%KVaTqVE`0}rJiXkLI1DK2qpt=k zbW_hVc`l4aQTLy@a$%B@;!$|G|hhfI^9!8E3 zvA8ip_>8JCOAzL+!R4Kkle5~t7UR(_<6@aIRl&qLaIeON^hO2?uJ5UeNjaOWcUMv9vGn1;v2RKCuud~TG>d%!-bZHH`dK~I=r z4Q23ozFJRfnJet0p~Cn4N1By)#^er^lZuZ9S6^m2Q>|>Ao2-|tX-D$F%&(S~7?1PI zavv>j2zHG5lc%745yj~5hj7K)ozha`jZY{*Dhp9Jt*JkP)iq$h_so@lWS@I^acLq9 zxZKMyaqQM=D-I=*WM$$gGPYR%`_<99#BF*5V{C1(R|2n=fPO9DDJ8!lx zDA4%m(m&YoAD#sGwio)w9TA;niY8-J9cT`12A=lB9Uxz7X?b%vpGuC3l^xY;PAwv-t7PWN|s1G9bHf8WD|B3?Cwg{onS4s5cyy8_g@BB9JN;k zKi;Z6#@r@{*?MsT?Aky)>=TFc0mn18KKFemR7jbZ7XjR-D-tv;kD1}MJNTXdvQqy2N+Sgyai$g}o$a?YE6iOgH8rE>ay(q~>P=Lo z4X+|C79uasl{!~juDlwG@mj!bFF%dgdi+2)0LK^Bb#kY@7TPt%5ri4>vR4JVj@Q-t zdM2RG3lp*?QWR15r1kGH_cz`%p81aJxZO%zw>$ukFSrInKBampst`VTUAV4It_ z*Nr*8%FhZksNpLLr{3D$b9MrOC*j-e#dNq$s-yH!u1Ibr@|LIam zh2o6F%aUb0Y78=-?Z~wg-)|lB1_;)a5DZs96^H71Ss+~$9yJ$N-~vJTtUE#)NZ?0I MSn_H16HWL32FVo>_W%F@ diff --git a/ui-ngx/src/assets/help/images/rulenode/examples/related-entity-data-ft-2.png b/ui-ngx/src/assets/help/images/rulenode/examples/related-entity-data-ft-2.png deleted file mode 100644 index 36c526928d66b75fb8e3407da6aeb572a183497a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76684 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

#W9?FkJR>v1tnDZ9DMNSfC z<*<{0-zGoa+5gzhM#$(vi_9qz**jQ<&S81Z7qt6`DCe+kVcLUCNlg+E1UvJ z&H_|)BL|EkvsL$~F4!n5SKQ1hulr4im^7sK@jz5#i%j-i`hQoJW0+1DU3XMKTf=O} ztArtTg#;zWC06Wdu*5FU^FXP#aN9n7lxe+V2{Cb`m3f43 ztKGtMAKI&l8$LJ*q}I5kM&lw6X}V)KFZ)WbI!A@C?z zhpxKjR6-@y6uy!6GfM(>O&&JI2pVK1sU`B$`omQ#Rmk+q3e>PZJn$twzXN%#I-Pzc zcsu+mm?y5412_BAKE!i!yY z7|@0alP^z0puc)@VKIqpUNpAy-9l0mB(E`Nl;&}EA=xCuINhBCo}3jG5}T;UP-IgP zp2s9Ns{2y7S7gRg2~18jQ>3(N5pTi~7@r&v#1W#6nRTUCk#yuDx7x5bey}r&AU#NT zl}?3++dLPWZ_}?HGG*vk_yv^XnCVy}l~wsd2N&+`kO=xgF87Ii+^?mD*uU)q zal52&RaaCT<~CvbjoN%xDhtcq7`Bd1hCC?(?1;UaNw{6DiSvZ5c8nG_e#bg^n!^i* zJs4!qmITAG{Lg|{JLC&lnHJ>3$;0t!(jkHBtdUZIdp$Vvpf=g#Lfmpf2$%@0!GECM zgi6tdw55Xi!E_vgDKJD2wY`Q^yoWONCgxETBuo{u$?^-+JC2a8U9^>upPu~3o_Ygf zF(?=?g{iI!IjprmZei?2k>(SvWvJ7TgcTjd2Qgr`W#jwJQ5m#Sz^|``2l!C z{xuD7gGuA^YQIv?HCMEu$-IqzYbe$b9v+2CT{|biV%XFJzok+z4H%GUlQYOjgyf~R zuicO2v%*l)h#n*kr_E>0C&0p*^N-~i(wD;Of)|K|!9ruM=FoK(? zY&#HNYG3qkrM%V%?d6x-CD)3*t@;W^C$HAha&GlqN*A|;7uHz$WTE*Ptw#pIZLt4| z6}(ySc%kp6O;1yB3=O3pf?_gWhj)cA$AGhNUaS${g8I9x^20{|w^esA+{h$il^xlV zyC0EgQqFgLjY)~qQERC@bIx-Nki%s>}q@ z>RpcVRrkhXH#BbHNc^xCZkJ6g5JG}f_n>Fok=lM1dlM>VHc_PcK_HZY@4b%JRILzW zxMI6JotJLQlvNDo%%?^_)@`bzr)eBsYg3iUKBQr^rXys!i(5$YVw2ZxA7a^Sbrq^* zNfs)#V=brn&sbuw*kXk$U{FaA*hOM-4Dm491hZci$y8*FItg1Jvs92v-i~~ui0m+8 z_qq7-i9#c>nT$$o`Cxqm;UX04tic);g6&Gif3?!h{g2gEo<{~XXD3ktTjgR?7p?}j z3zI8haB@-Xeh;-bP6M`zpNci-0v6+uKupoM{2PjX;fWpxv|Ni*B=KiaWd&t6G#DF! z@gdOQ+zQkWlh+~#lnF4zBx|=CJn#u(@;Ks#c4a=RYp?Mu7?p^T;`@$na%eJAmX80T z=Jt14CQ^3`&@EiRK4Ui9E3}1@ab1D!R#x=DB9Jw#+9PDL-8dArS3;zo(}6iho4^wU z*<2CVtds}CFWqH_Ie3NhV%V6`8~V8gHJ1xRNTN&ydYGQcp#fGaNi%BF*AeGdHq^At z6f*QA%|@{yu?z$>Bt%oHb}q`1l^W|&cu7|N0Hcmhj5jYJpCB3SZ(vPigLg$;fVbj{h4m={@Y|F zObY=`6E;P;roFaE#DW)x8`uMjX?|g2sZ@@T33u%1RwTj5@sH7w(AsVDjJ@rh8%ZB7 zR_hclKh5WY&%u%>l0j0RJ7-%u=ofHCQH$j>#ku6aXb~&sr_H{iEAd^@g(V%rs9Z-O zXa61&dpDbFrTO-xAMG?PUU2qv%&J7pS*asFFrzu02ef$D;BoDm=yr@YjnuLqRDFoS zWaPjmsiKVNLjpeR3lX^_f-IF=F;z}9R5aBTnh84UQ(rs+k}HDz;zL&GCBg|Av+*j) zz`{O|U5g@Mp;>ww+!hz1?hSd9w$@I%tk$L|%rjd;B|9cL3~t=UN%GnEZr{1jl;J}e z+y3!%P)+OPO6F=tKM!5??V8rr`&ZmLSh?&S_1%LOlygayViR9FQf1I}SYzNPg1KlC zaS9gmhvfgNIPNo0aXjFC`>MpKOi$zLG@B6_d%1}2@g$eW5}B&tGo6FxH4BY-U1z5CfbOvq{(#x0^1-gKVq>F->yvwJ)_ ze@=zWDMHjG|9koqDYUXsryQ+Etm&RgUWcwLa7T*eQ7O|8lpngkjTz^N753Hk43aLI z4;CWC!MeiOO2}qpvFBrxzZN1O41G3vXGwF$2}cg$-e#H{(s_u(*KqKwEO9z5JrZuw z^YnKI>&kAyqIatRE8bRp)i);^0Pr^WftXw_u>4Oq{#x%g zfcKwbqkCB%qR-7GdJ)}ZSndl}yV&g$!We8gZ6|pm{C7fwX-*=L>fLzG{Bs@Rw_7)G zDtEE1tup*L2dLO1=Z?9e8kk%PCMZ9frh~MV=`;Jye>WJey$LL-bm%F6nm;di5cDrA z!W6}PcC<%9`@FjOk!U;)Eiv|AjG{Fww!!EW+Fwi;PRvzHTINT?$rG0My0wF1 z0&r#7mM7&vH>x}}ZKW^uJeYI`oo89@W%uos;ck?oT+m6S*Ve>6G^D2|f%X~!JJ)9b zr@RZ^c1;S5w0P|HYb{x20rOxZ%swA-UIv0M6(oKfoFxG3(WCutMz>oa$^%3-%878* z6xvT~C<9-IRD9MTohNGp#lASsEk^77c}1+Wfn>Ew${V(nGHzo1zS}FTqwlw*GO5SS z?m)8V6Ru_Afr}*ots|9Cn$0qw4z&>WMp^I8w>jg$nye3W4((3%77qZu@{z~FYK%#{ z0|1(dYGM_3Evp@$S+xcMxafxxrinWMW&QrtntiR=)Ga(gw#P!?k?%_mX zay1Yh-vGSQ*U@^jjsE(_yYm$m(AYO1OiN#lKT@n5&}}|Uo+IX=>7WgA3qZ7Ji^Y$B z;&i}Yn(qvsHWD6=OYC*T2C|PwfgqAb!Q}qbXSZA}sl6ZoaC$uv0qE>esqK@IH%i#R zawehB^G~#yNzPAaKFa+qcW#hgM~ZuyS-qZw>-5{)^c$u|DlgFBHm7>RupB^$F898$ z%6Q#UI9;Kt_uJ2x+iS%h1#&7h?{;k*DE|k*wtK8E>3m4nZNAwYqz2@lb~k_q>kag* z?lE8Q>F|xXa~F8ky<6mLK^|u{BIW`?7p79SH_$dzj;-?%fJf0ClVXG7=cs)jB=9zp z$y2m=O3lmh?bS{F$G2!#l^h2Es49SN$N)EKr!P4J>@ItQ029936mawwbbBrCGwfP6 z1=N(9HyGvmFU4_JD+c+?L?THG^d@B zR0BIr2#cm6b2tctd%zM5*ZsyS=tX2jW_n<6yW@|+Dd!1VJ2kl9{TJJ#aD6TT*vOL( zPJP1t8nfrmLp-MF01;gCy9B?2VWgjUoJ?wQRc2+I2+Mm?E|?q#kH}w?}9Lr zn~bPwzD;V0SX3S$HynTt(Jk;ttAN#arZ2RUy2v&MZPj?tb;HC3z8+*kHNdTyobHPf?UzC`SV|d z==hH6V3bpv1F&SMLKCejyMIMHQv(+Ica)Phkc<}j;`xR ziwEqEeml2s55ISSNORU}Q1B&@4wS%oszwo|2e?Sx8_fn`O)g^Q6 z?&a~(TUZ1lz6b^hM15@DJ?wzonKSdfom=bub}zWThG|S;M$q%+u0e8e)N|Z-ZZlt= z^S!$i){>J9L2=dlZ@u&i+W$c(*DQCC>cy&HNwoHrw~Kf!4@Nd_HlWcMtD?FI8P2R`?rflzXy+NeKC0P|_o|E4XcDw~#@kriIV!6QBtmqi}McKMb z=&#p=8HwYpJuRO^63pou-ftK4P*B_`||Qo{%G#<_={%(%7IUOWY(OW(j4h&BBTC6tpz{U!)ZR-Ea}F0L!oRN z$&oW@mdcNPoI2+jkeA65B4FEdItThuZ3#46Hu;}lh9_brIj#1mSushUtG3zya=3LD z{z=eTK+LA^pfCDdCZjhI4|{j$7CcotN)FNshv^HCvRTUj(4OKM^*4cJTaA%kyUDTK z5hl-vFLPk)C#ve|uS)6n*G=5jfD*`pYd2r*>A;7K8iYM?M182nS(cVL&>om=EVzB# zqo1qtq;Roo=z606U~qZ#ut@>=nnOg1kQse}b#MKPIft~v2z6%TsZt$922v)Fx!4fw z8B-%WFGBxqH~S>19!AWTDmiz$5~%6ti^~G@!bOTQ6U#1#?>K}kFI(@9vf&oTe_8c8cJX)h8oT{}ZZCw|q@ ztTf{ohlpmtP4&{-Td-^4+^wyh24XeR&pMKKY6zQa7EvF%AaNM%T5b|)V$N?dor%7z zK@xAvdNHwTGlaelBrA&F*gZUul?u8L$Mb2RXAQ0)gl@-ojo!viX!E=r6VsgGf5Ds7 zRw3A`@>x=8NGo%f|IS^wY4~MYPs37AS;IiD+~~W^29?4Kjs*nHwX-Iv$mZy;<+^lw zjl%_laIpS-Mf#o^1{TT~T_j!CRctfNx}rwIgAX6ZrB&@y=}FV78mES8;jp?cs~M!Pl6!92)A^d@jzE81_@p@`>76pi8#~j8+W9kRw8V;%?<<_vq z`oC6d`Pp3GG5vZsBVo-~E2zhAwJ6f!3DKc^RnKtcV?&Z(Bi(+dkaHigoSX~x=Ah-< zFL)TNm7zQjC!3k0XbXk9DrOK15yq@yhF9ps$Vd!xFAP2#+=B_bi!w|$kZ8+)$Ls6k zb)4`dVcGHGWPuIS;a|yBEmEq;91puXIWFFfL!~C!bWFo}mu@k5cTaNv_^y1SlH$lE zyyMsK>Kfiu<*FMzWZ#wZz}X;9+~Sm;wY<&OGg?;lQe*in|IX=MCV$%I>7MV_t}A8K z_bJQH6FR1p%ttoQ9wePPhPt*L6@@u5KuhPQ;2 z^=HmikZ_Rq;sll5UEM$&Y}+Kr;*#JBQgzWTkMW-IUZX!#QDKwUhN|c1DyOsaBrN*e zE6px4Er5*PN)oh41JhB#%RGK|JK)P5cxS0;ym{v0-viY<@vd(nM-M_PwZRo zo7FvDu`iXDe?`(}73hdFjNu4kA=H8w_p=jZ*YDt{FxFLs_8q{-2o}yWMcj|X^}Y5D zn4)h=a|0u}Zm>Iev9|ptWvyuS6Y$%0{fmYm3lnUu0w7iJeop(Dn z628_?bi~dWe-D^%>paNMwNt9BkZY1&Bk4(k4aK&|Du4IZnWaF|2S3dI>a77U zGUkG?GEp(WvP;aO7A@!o`45)*84*sx;>md?6Y++iUKqTnhX%7y=JH6N?tbQp9Z)FZ zEuKrVTqTyP)Y}O+bESNvC@$}E!F$dvT{@i)Ta=qC$@K-)WIMbk1{fYH8%VgI81wQj zSsDfq^{2jV{KYLA<-d+O>Z0UVS}Jpi<8Id#-BTg)Uzi{3b`%Kuj)`tY*gk0ddCTK*wF>k0P7gzc!?@Q!O9V?r8Tl)$n4tL<(w~$FG&dV zSnjXKBy@M-F;UTPdOm$>oqTi2kxD6e&3ZP~cd^jfIh3D$H{?t{DM!w^JrH)Zj?BF7 zFp~RnVcnan3+|w#6$IWO*VTJ7di-gOy;+rH5m0C8p`3vWz8@V}0 zhVCViXj>0RQ8i7#4@7gaE*<1&L+|L`(QSLOanDX*57-1SRkgxZNBIYIuxn$mvKGau z!(Qs-^}J?up!e0b9z+cv-pXsQq-7w3Khzr#dmE9T>aFwM-M-xrKGtvw2vm$i5>21C z@t0Gba9;2k_PbcUTc}B5vge^xVe=#VYS%ANIG=`Td*)>2q2gg?mQA4zZ~W2?_Z)D1 ztE;%rAbD3$9Nl5v_P1h`DzsAxyO-S3HJ@3(MR=XQ>aBGb%5yv{kCA$b1atWV>5t*D zdw52EFAsa71^ij%dnXz{YDIAj0jrU$(@KwJ_0G=D4Aa;0NVD;UIs|N_1D+O0T)J11 zu$Iw~58*`Z5a>=;kT49-e!pXzvM5qM3%=@ezMKTpuFy=Pfe>fAneE$LIte^Ui;dN-Y{6~U8zZ6yzOI{eW`5I9PR-Us_*vhTAuZoSiF|hb=_~R=2Ue$K=SK!ls;dm*IHW>01wcL7 z+U!DSs#vjm22{RA*9bQ{%xFTZZj8(qZQbKija|Go$PyBV(xl*1!Jyi!5lkzB;B(P7 z1yY+|C@tOQn4NNrs(I@jkZeApz02P8mDkMksd)ONr}?BV=$;~g@2l>k`N${j2<0_|&gbZf~% zYQm%u)q@}bBQG`&@qB+;t|_MK!iCeA*|E~SLzGIJ*JeOc?M0fXOjC@%8GR+`N*6hM zyh?YBY|OfpHtxhtC@RPnHvDF>+jghm4ed*7u9bnR0>rEEEPt~tPnz7!D!ccT2;$)m zx7W`*tKRZleEe|E*?I)fIq={A4Q^B|{z;rH%WktE!U#gOcxY%()@ z@Q&U2+3T28&wnPiw3|fkjqE)T=NeRDC=eeIrTkq;L+>@?FMvq?mEjEr%j~gi$ONJM zh1Nn2F@vw&;ENH5v~KziY-w6Yn9j+eSexjt<8S|(siM1O)^8@-mcOxkiI?P0*5WcB z5#n2;xba-A(y=Sq22co0ct4@Ub$Nc%Vu-B0 ze2HN04Dwej%vlSc+(No&=P#o(IRohSe=*J3_KXePYyW(+#G(Cpord*(WC(2+ND+r} z3C_9DJq&zVe%ckuJECLdH+Go(Gb!Fa-gc^!Z1~{IE5H0e=b$E!ERhERKlgVx9+&t1 z7}H+v_m23a3VkT1KZ%|B$MiKh>HfMweX%>$jfe7e>gT}?1cy;B^}S?HV}WejFB{D3 z9G5kjzj^~%b^sUcJnzQ}}H!O*QcOQo_ zmY*SXFal6L11KihV;1Gp(XPY=SF_!DVdcjG_DsmUf+F`-8k&JViP^o)Y&t$Mk2xZN z%g4k^RDug@Th6vq5w4XR2>@lvpVchSk>XLBBMmgw`Do1%;n znS}x;46Gri1=8%Xc!b*ce3TTA=PvgV&;*g^Mz5MqOoI-`jt%C5*MqWZQ4w5|W?}fU zD8|C#Ygqnqp%vc6a>#nC+7Yqa+9LA1nK$mPhp(+UQo)!DHD0qbkvs)H1$Q~)7lqBt z-}oBe#yP;kET0(Z7^;+1=mse6S6 zD`fgsqWgID zmvdcx+SBRlSR8DBeNsN5mq-57Y%2Muk$(K*uef`4$HZ{EIy=ED*9k*7!kf!)kmHkO zR>k79Tk(_lPy%pjzdtyln3h|kn(|0WWSAWCyX2pzi9Xq|Y&N!$UA6w^=&i2uV#j~| z3nqkP!P%ToyM^`M)tiR@oVaQ$gzy$T&|@};bn>aW{>VRP$c7c7`g#MH8~pq5m)F3Fb$J}#G!yc^Ti;FxUNYO`X%Yvd(4IM!eo1Clx z&~}$qW3IoBhy?-Iv~4dwQ7 z@^SAS%O#YzB-8Q1Ge(?oV}6sqWRk4;YurRR7PjU=N^e|9u(BsTrX`ejMU?4L(qEN} zgK2r$d>4Kfj^ByTn$3 z=d_=il5$BZNr8upYmH+>8yh&#^6~M_U^LzjlcvKDKGOD>?Enoks^Pbu6 zai&)9L~wVWW*mp#`D)DAz&U({Z2!mStSZ#cqGv(^;$sI-RHEm7wtF~*2HG-u8s7Ac zr>{qIt$vX>IlId~iK!d$=bdB2AUHWgnCCGTIy8mV3vZ7XJj;vgGnG; zJgNo3+%~*Vyzf;p<8F8qUw^rBFFL4Hbwnw&<=&w^JBmeW% z<4|C);W*+faOdicNaz)&u9c!M_M015R7ZBnayJH4p?8Qnhv}}Z89#ChcFk|>o%&``1g=~wZ~4fFe&ynJ9A*ZZ zkewsfAyx1Fo$ecy=@7Ao)D!X-LBgJQ5t)mJQ?H6CJcz*kD)~xbq9H&3{{2{i`<*MU zs6GDo7SfdO?ofA2LQqcAXLbnFk)wVJUUGUdrruYR1)IW$d zSINIDj(fWIb7*FNJWC}Z)ckNc_hj=MH*?j=Ia#i7IEaX7hXK@#GXpyHh1gq&-%St3Z9;92GEm$T! z30Rg8IDbs6PPQFBk?72cy1=deC5frVLA1~|lVx>y++iMg3Xqo|hVQ_2cz> zuV+w6UFfytLe9%IH=+~S7|pJ7q=q}9Y;y4;>YBiV{fNb9IWQ<<%sbXUQ2I;t2e*Wr z`rI-Vu&NdIUE%EZ%Y#AuWv(Eq>si2}scVb4xqai){>eci_x@y#N;kPR-9#n+%tCaD zph{OSr8?a65{EQqYs*k&XeMh3?0Fa?uCK*4B(w=T?|+WQzdtC=RE%b+Ebh^_%8W?6jVDbL3vR*xBLb zSFJM_Q*AtfKfKb5)@`NcPSIdjnu{H354($Y*7stH(2{<$Kr@YKvwwYfHy)XiuBz2$4mk z(e~IkX$16cWxou!eccaiihpjY#@=6>au;#<{ED-bm`iFHS1lU9k;UDcwX#gE7^%vz zc7*+c`uM^#ZPh+Tg+S;ttho`Pb#?iy@M7qA(hB0*%cR$f7)5@UYz~r-SSs{bNC+( zj$T#Kro!ZK!U-~gR=9}&Q2F6q{wyu*r~rq?H_8x$rti$L#}-bSs`F3mJrKFwg}Mg;{IclRGsaA5@zBL5UiVuP7@c%f1`n^= zk%Nr}mFOA%T#r|xE@FC)?d?q~F;sJ?Ozh09I7;g7wEOai#h{4J0=tnu^ftZQ#-x35 zPt^lHvEh=6o7+PJ6f;q`;U5+kjdQ*F=+ZjJgJgL;h-he@?_$4lS~dIGfr@iQ#<&OG z+g6zd>HNnta1hZblc_?{y-~)t57sID0xqOIs<#0(0l#cM(XRtKctfUDYk!^2yQlsP zGqQ#QnYTf){Fy4JQptEmG103JjkV*`$l9wndHwCJ$5^Wg{f#Z0UIt+2WeaRMdg|j{ zp%L-1vZS8w+3eeY(OHmD9wx9Q5;**ve7x}$omKqLuL7%9xq)0itm^0ByKzxYlx7A= z<0Rx%`ophcW5Zlv1=6T^<*>knuft*;;r0D&(JiCU0Y;viSKBkMU<#fCHXWwz99i-@ zrZv%y$y(FhSGa;TExg+L*0Yy4*S*5c%~kL8ndjG4>Nrgfthy=YibIFKDj+lh@C4@_ zYYv{-$3JpSg?VJ~$ff1l=OCRk1}uk;^S6OPEEP?`{A&IUHJAi^L=`S34tLgCwT+5^WGRVYvQ}x|`!9zIaLq?xP@Ul8s(^=ldcvIfIk*xjVL8-J<93_@ zdlu$ZtK(nF59@e1_Vv$?>(us&i86{3%-UJOfa_abf8X$T>?EoE>x??R+#Q`@&oxVD zk7Lz+%T|fvd*z+@4q$iK&vsLoonCKu9?rerk6m8cl};&|#qLZM#rg(ZPplW`9pj(9 z{wn^DtGpA}VFnuJt6%E$t6GVblukio#ZToiP zJFEM^?}n4|6aI}Slm9+$cemNQSWaM9o_tZ6arkTgB;)_wpdixd7Skk{70m$YPNH9N zpN}`g@4)8Rd!GUO2`hL!!n+*(6u*P1FPtaePR?G4jEH#1nET|JTXDrl?%|jcO@+>H~?ZT0(Aa$Iohe;!NYLf2Ykp-0J>q~f1Mcb6U>@HPs$y&YUfQ^@%Dvy3LfXr z3jlgYj!(re32;l|&#dJ9#~ep2V|fkn*Z_suz~SSyzbOZzjq4aX3s2^E+xymAOn?#@ z0g(OVOO>d(-s{B^2arZnfchu{lmI%K!5*qc?T(bM*B%m*lSi|(wbHp=2C$erfG+-8 z8I-IDAPiuO4QRXNQKuz(s)(-c-j!vO`Lk*nCNG}kweE`J3JM626G-ecNJ&d?0_auT zz~?tI=;o(Dlos^AF`5K;D}D+RV&Wd4O3R^BO9R&OT?c79J6_+HFAF^m9cPBRVGyrq zSv1I^ypD|xs`WT@eI9N2-74~Fiqzi|3NzZ9)xlJS0R+A$CZMpo*gbvzZ9`Z=np}9> z{Ul+>NY<%ud>)f^7%RgimjFzTE1utUdZD$_VVPU60jsICHDM1R=rrV=p>hL>cR+W+ z3xI*%1cX#hGDv`1N^OA-z^A^v3`D|H08nm@Jo;eRykKgu&>%~-fG*i(?nEcY`$B6J zR3cc!VM!MNI6(@Cn=(SF!tUqgg2E0c1VvhG)X8hm-jf967Gf(0GgY-+umQwM{1*Jk z4y21P%En#Ia{>^;LW?0@fD6R|DhTyJuOSHI`~^yBL-1|2$`{$Dfp7lUKktjq zPhyPx=YU!P5h>~O#2k0S)F1D=p5uiC2Upxh@OAi{7}cUD#t~yP_sTEJ>VZn&Xp!M1 zZTC&tK7x=Mz`FNkxY@SYn<11w*trnTjuqmGw_3GUc?l7wG^<3zcijje`C=hn5=LE# z!9W`0>Py|}rr<1qZw}&X^`DK9K?{EXKvqpe}xD)9lH!w(#r#r5i^@y?W zMCUhQ(BsF!3Gm{0Z2~^=y;VdL)FBf$JX^;5xSbc?2NKfKhMVVCv>;fSplBeT5#%)P z(u&9isXHUyN}%a<1X{_>WWqdof8Nq8Cj?ru_f!$~*!BH=P)2Cs3A~Z=b+@f6YG=T9 zp@;Fs`uG^7I7gI+G1{bDh*cE|!Ozia4A2`bw+#vlYr4c$?F~@TRG$210E=S~#>;$W z7k{|biPw3Wg67t?$Eyb@I7<0Qy4%T~A+C7OU85%4*2NW)MEW0w&HlOto>bavF2DFUx)5~7Rv|McG09NMf-ghPFd)t|o7L!6I zq^TNcnAPCZtc(z~o;7q^tKNP%vkL9*TW3Qq{#^f_O(Jo)!bi%X*0aLSKpnv}(#d0p z3PZDcu@nL9Qr}%Yb$~tP9V(})>kkH6B-hztvSUnt&MCsW(?hB}+4li~K+W!twhQ9T zmxbS_7z$l#l;x;j;z06MM!#Ng+PPs5!4U7&ao_Z>2%m=O?*P=YkO%TJu8$tdbd^3A{<-GX)teUttVS0GGwTRpKro(a9dh{PFfYpf8wn~bDRZ-~hshO5Krdkt zhV9xR&WT)HgfD%}2@5J?M^?yB0z9?AwXypatTl%#)Pg9x5}zrJ%f-`_fq{X>H;`X5 z!JL`^wD{N7jq3ptwuS+W68Hal8rO7*Hs0RqWP9v_U*cEZQ8`$ zXuM#V$r}Gv-P!mBXAX$xP+)CQ0TO&8bBv~bgQh1?dEu9Zg!`6Sj>Hezb_yh=KfAW0m<{N}QU$r04_9%xrsQevvDCSJKZ+FV_pz;Tb*pOF6yE{LH z$!6YZD7AgbG4s{bX2Xxs6;# z3UoDJDIEif%Uck9{HEZjGji}AHKpm%O%Y6fFUkc!Dl#Q-v1Rrdf6?CFh2*`oKGk*} zdk0LXf`%|1M=0F^Ip?$3eK#t}(J=EQye9loi%WZqJ%m?Akj1 zcL-f}+M4|uzzil+nnzuPb->Yn$6HS!Y>^AVycZ3CL03?KG^SDZbu-Do`+Ee*)R9#3 zi<16a?DnI>X2I41JyDc?eqgO~-C2?w{KlQKk;XbGBHrREqzX=$OS5v?>=}@>4LZCN z?Z=e(TQ`W(ryKxClHz!>%dtWojcbJN3zW#Li0v&A|ztftg!QD^r z(N~JraB~I7ws2X3Z!$X$KYwnYn~BUTqSinK)4MnS=6r%qDnIkyr)3;da@vLNi(gEW zKSKYw1Cm%`vcIZX(D3KCZpf!XC@%ZP|1HNcV44!1)b>7aoEE5(j}rrOkpD)Of%;eV zsJmryQRSZ*1l6cd(x7T{C7ok#^Y$o*fJWJw=edYQ`Kz*Z-a;x{^xu|az^sToEmJl9 zUoTVrH$P7pR}fU2Y;A2%%0^OBn8YL`^?R6{*f=;${QRWg1ebPp76RHy=W1i37A;0y za*{wk zmHAFp^aWx`z>ACoEC=l8)wP)czC6c3bWZO0XejhN+H}O~GL)TCgKD^*)EzgDn3$M~S2i;P(Npsi zZzjv?hgH>Y92v{(y=t9MJ)h$Gt6*~!BAHKt>h0~FyZaWn*5?J7nVW;OYa^&`v#B=j z@{gX!xpLz}YdDc{78J0W#>!{V>Q(q&(v0Hj*J6qVdA=KuXUW@@KIv!qeGYowt?R4& zu7w43spKC$dL``exp){q$;+P%N?R2gTp4{r3h_T0SMC4);dBWOJAj-B8U>T;-+@*m*(AtIU83t7|Cq zAf1~rHprO!m4}$+IH}%l?UIfH?X0;)`~6~38L?J4;8B!@xac9DxFh;mG+f@@+#_80 zK20SyQ01{^`s&%=q(PW><_lAmDrTq`%WS!q9&pmEHi5j0=oLD#AlXoIAK!u7;-DGB zV2L?J^ewIUIfpW~?Whhic9jmq*BbY4YvBYDn@cat_CKq$NYoFEJuOmW%uPJT60n{K zUF+_sUU1Y#>o4gZmCO^aQHgYpO&2GkiwEdh6X^3dvkRMWob%$u6`*UKTQ`<|Ma@2v ztS2Q|qFt;};3~)S9gN)rVyC14tmp^Y*nX%4y$&rG`Z6P3xVVBnlgH?Zh+Z?cK;Z{u z-POCVMYG1*9@L}f>sPIT0j8KpdNmO4|6>-LiF9KbyOz%eOz(`)$4mZ=6<| z8>LPv&4$dM15;rMNfp5kOW(h5E|Rb))PWeKF3NlB%|aQ;^h{?D-Ghx7F0Ijv$GI1^ zA0XDnooUzytecxE*p)hy3H0ftk3T(1426D)O4VVM7&>74QS<9QJ`Yc7;Y=&IV&(&6yOQY$yhNLUZ5H2yRl*O z*>Z#&r~$lqWK8+(B*X#2Snq*m!5HrNhbz! zYCkUSm6&5Co5Mk!QVe2=8vzpuv^IkHOJSqY=ZdN6sSFw-sB|2zA}9KYmD{3aR(h3U zizeAYp0nQvnJh4nW}L~Qd$*I--1E*dsA*bGv4Qx}b#G#1*H!F5(p+A6TS6nU&^|g2 zKGafWU2}ywL`HWcUo9;DAX~lAhIMiOp_9qYf>Vnpsm(Nrs})acw1qI1ioBL+i(0!L zatzuw>jHD1rBQb4gg#||2r#-EZR`cQKtz)gP;jIXfEY##_N$;291vsXJm%7SCEs_> zGj6=sg^F!rJxmnxq0n~50C7s@fqdb~L{6s1X!9PP2P7%u1{0xA3Mb4c9<2Q~lAvUd zU3C&inMR-B3z#t84&*^wB{+C!Q_(;@?!0s&a%VZPFB6*JItUjEsipXX1sJia>ZHBS zk_@v&T}_`8*V)`}EEi+mPm=Z8%QV()38Hhh8$H}N>T~Q%&q`jkG4+4&2^k3KsU1?j z((ARChu@1Tm2KStc=pghpD3HjjeD9(X~g3+z2|wXxmrQ8JKiQf(8?Oq{N44mLjW}N ze-J{}0IF5C0G7XTIDzx(?OThs6rMVP74a7|HC6nr%KhxqyRAC*=3%DabK`}-=efOA z|LwYL;hh3hKC@01U3MaowFQnR5fxR4u>G7aV6Dkj6Hp6W2C*mK0CIPA859hMJ4At~ zt`eE_%W?{D%mm*g7a*b*C{K5=$9Na;4~x}rm+uanZ_XuH$HN!HGxw5`Q6M@`0}^p9 zYVYsVzdN5#$SuCz!8doWf^@UP4d^KXO}VRCDtYl051D)ozx6?xrcM;*KjHe z^7edpE655(0)A&@C^pbB?Al01I>9G<6|0j;kGg<`c;V(~7V>IRm+9!{xT=B|@eIHq z6F1{cfjT@*Dti0o%Z*_-9p6K;*z#-K^UcZS$Hy_Yj6AscJSYqMgD1^lWn=d>iBftu zc3LbchtOC2<_Q`nRVjq>m6|k?k2}_&z>zI_PUPqBuMb_}Q8LmAk57<%?R1^%o~&~&5Y*`L1L-inDKWr_lmv9yaAQwE%axlXpb_d}L;~)M z9_Yz5LU}+%_Z_9ofpGN z-pS-B+IStqkH^ma-4wU$`+O(V5q%G|tMA@v60@bS^5nlTFpw8Q6zM3FGhQ$~UK-w0 zq^HP)916Hy95CfBmD(6PwY`30c!VGpYYqRdIg@qhvRR6JD@*z3QD(57&B8)5S#C9K zm1u0NebL^xYg~`5+1HSVX$fu_z3>7-x_*s(D|H7Rwjfzx02UJmKUSV=YpgPE)jdG? zzWI~nG!_0O^fdfkO7>3Z2Q{`@r{y1=L1rv1^IYmhP0npz2R|uD^bIU$4a3B1cEfg@ z9+D1VUn1wX=E?U0iMxHkYNXMx0de{qRikY=t2g?_FtF5ERK)x=YZdc!qadVY84WU& zm6uNg%7vveXwY}*C54VV(J0>}O(S&1F9&<=y0zb4^~T_;$wV$)YUZtb%ttkhV825m zmq#n@;kZ$82fbw`b~}bbF33dOA(zkQ_M@tKgJGJXa-xq6hmo>Q{afdn)XK20BH zLuI@`fsUK05+^x+AEOrSfOCRr?+<%Vd3%|U2{|9JqdMv=TKonKJ9w0`ME+P=y zU)n$vm~ujOqG?q}@X5h8w=}Uo5!Y;mb!Mi_8|hMEMfc2QXuIxLKy-nl%3=|qC#f)c zsm|uTVSDrxNT=~K-hsJq1<#Z#1W>-asnim546>$iYF_i5EnQRMnjX%RJ~#>sgnLxn zy?xyR>QhpVtgteHtx7yEYg|v&-tJ=LRh)@jPlvbc%9l~{-Oa-~jvt6o)y6-mq59*- z%XOV_mFYPxI_kD2XVjMmAtDhgAI=$viAR2ao;u*s-=XKc8J;eO;HwVQcBUd$LFy0d zDtMqL!mH!PbUxc)7|00G4+-mm1{!V~l-fz#>mg7rtNWe@dS$Y3fvsT_{|(sh)8-Tp z_;?R%G?#A3G}seljfMdIJQ$=P+b_NV&)GxWvTDyI3Aj9;3|g-MpuFx}M_h~Aq^U6T z{kPl~&dUlmd*(eWQ3xW09_dkDX%^eA-tH1*c^fE|?7;gW$nBzu4L3P#0C0*dxA-0( zv0lF(D+08>RH+_zo6 z)u;0Vux)BKTd}$eb5oGd}nVgteq`d zZp4$MV@|K?p1k8QbGrL4x5?%8DMYXQD}dZV)W)o){#2_NFZG8?X;l{*w{cMgU*vP!0TcO3mVAX zcd3OoPJ(Q6M)kM!2Qwg#>Wo~>R2%}Sdfr29lN&Ywp)TUkVO9@B19bE4v3(s1w5om_ zb&6wiK#FlJEA)4E8!I@bti_$#+2=QmckkGdp*p&`V^2_To9p@9WmO@bEftV&=~qu! zdDr|K!Z+xJXOu|f8k_nYSs)Hv_sO{YMv%~k;J*2u^)>Q^(T}x6llBlT9M>K}iy{9# z2_56Lsn}FO1XnWNF`4 zLciUEw=|XsHrp%3P|mbieQT^(dGq8~cFCvu?uiqcwuW0Sh7o9s@`-oGd!(ioo93p8 zI~h_3+T@818`2HjBT9HILhVE+C?Uks&bq&`-yW%oPE5zKcKKZF@(J`~GB+ZSmhnMD zoEQl7AI1DZ&uN{ShkAA>9N$$PceC)ZMgEtDPf5Vjk5%#lo|IuSW1WFWmE-DC;qDOb zY9ZV9oRW+94@R;VL>T_NCQtrACR#KqE?wlH8NrEBW=p#CES2TY_13OW-qeckAOG5C z9W@Gq=FY;n`)fouo_q)r9+*gK+TSmWIj#znytzj*X=Gq^tW5j|7kd|W66hKSW=BW3GAbCn0T^ zo&>?_SQ@F?QI!aRgDFmHIDaO1YrWJt}zAo zDNe@<eoo)mF7$rT3`w*RJ<`$S@EJfaeROybtf+=gm@j@Rry} z{B=#<;LuQMclT=C!+Qz}m%o&IwJZk)1o-NSouGstrFhGCBg**R@6V1aLymmt%Z^`K zR$b9Wzb6s$5YP_rrge^+eP74*zh!XpSyx@X^Fn?BvcHUA**%HLkaY$69vAWOcq_yI zkI8_iK<8Vhv9sUiyU>{=nhy;VfTn-kNT#7SQ@;Ku%??s?uT)Q=>9ZdH--f1-tc_JY zelLZMg@fZ(Nd6zFb_UEvlDiKdwg7_nV;~6))59}?qW^&c4ljRVJBynK(*#s$!}s@{ zPl#{E44BUl;9O(?^-)mJlb4b46FZhjF#4$WFKN0(_rGHUuKIbCqdwf9^4glc*VNq1 z2og!gDawU^^JW)=B*(82l9KviV&kO%Cr*Ow=mXtGD#Timb4TUhrNOqj#lp~Ap#B#D za+;0B0HHsm4o7Y5m%I6EeG!7a)%nNL{QA{J8?fmyB)O^ja{I+Gk5XO?; zB%mwX28PPsuYLOGXR40|ZZrVt^J6WMn_lSl`c|S>=F7VuJ}F z7yt?Ge&IhahXIiPkjX$bTGBJG_+=Sb_HE?lN9f;2#_7{yCVR_x)e#hm+&H^W2Eaa< z-?^G{{1-z6h5$@oEi8Na|F-;Gxu_sKsGRcVmrIg>ny?WS}_$ny_A8AKWsZo1Dfi={U-=eTcmMUk+iY-hnoH5rLJ*7;W2d|)y~`uqYSKw zs_>3+d}7qD(9M`LtId1w#MTEF%KYE|PBI}1Vma&w((Cg6@87=%?H45hLhDh!yNetC z0T%`AdA0ix@Ar?&mp~I!+vV}Z4tLNRAXl>x6A0bN`iGE-KPwSz?=q8y(m*0WR%NO( z0RJ(N)XycKDYqT~=oz52W#T#$9>F?pSoMhP*N}mqzcd*5{lM*ujXLT|A)fN9&m3s- z!~paH5QYO2o_&_=H|w`3OZMFhu8&N65F=`9P;6AX-o6+HDm9b>h)62esZ^+HO}63az(@6 z2Zc`^Fv{!cn>%k8Cd$0%UH3e{f9B=kAp(wm!?$#LrXqz8M2?P8GLTmZfZ1_Bgy^S5 zAIQUk6^3unQ4u940|{g=s3vxLu{K@4`emw7$bI%Mn{-_g?>p5$ONW8`WUfs$#hH-? z1TEK=7LA{jyh)zIr~YAph%MCpLLgqXxw)AwHY4h^QU~)o4%Qg?%(Iul3jUggJ2i^^ zB1kC6fK<)nN`<%=*(+nkI6%C<{seT=Ht%s+_@h>pKuFAusq)AvnGy663FuA8=fL;k zX9#<~WHt~ldglRZOGl-Km-eXub`B(D^c(zaDO`|amaOcXPlxzRkN0N?Xu`r}pFANB zA-(BKb=y1@9Ago++})tgtw6V%G+_4Qqdx%MC5X+Ix@;RUtpPcfV9-(Jz30mdDR(Ez zZPRdD3me>Oky{Vma;je0w6zs-T%j~!;7$ys^Ps{*uMGR=X_V>re=G>wR82GDVFJQ1*6nrvOOG^kzL&$hCMnlxt%#^8t_E z^y~{+G{_z(Bto#weK4_Ywq|ijZ)svBRXHj$UrZwMfID)|b;ANdTI(7IN5^Q@A_H0clCHKYR}iSyH6rkDbBsz!!qLYDeB2X+<>sFk-U5cJGxEI2i3iJ}>=7jQ%>xeZ=nS&VG5_vPlUDXoL1&&>B-+cX)Vs zZbp~ADX5n0X4XkdP2FPPb@-F*{pE`sg@RzpD?H}e-^-tx2`ezYPSkN$f1ILUrWJh+ zY*?L{^dDzwjX~Zc;CaU6GN}JJJg2*_4h}q|!vW{)lK(u_1b~B8_ImNcSz}GK!3FI2 z@ojChyt9V-V!0xSi!Gm5{^7X&dE{U26EZc>o|%<3A<#aY>UJC4HRnGcOOoz7xBI@$ z_Vw|}$;r^Y0nu9PjJFD5l+25dA@;9Mnq68fyL@hI8qJJcee{&> z>@&iMY{-!=>ImcK=fHbexPH_>^S?eT#Ghm&bdu2BcIW%geQK)N&RKTL*JMT(i}t;M zpM59j#d18)c^kxMEI6DB!_;L%@otwFI#&o4<_DL7Lo>42{zCD=*nhc#VO(z=i~{Vt zMo{L_YqYMikznKzZJtNEZO>n7b|}f2d_Q6>3G#|u!ylH-ZUFBL$*6q(pUWbNrHQLH zCFY*KFj3^eg$x5KG8C8^D^+%1=*dH}ACEnn-@=QCa>*q-H(yPRLS8l!_!X*$qa*l9K2vpf(DhxXBYK2>m(Zz3c?)?9EEMq=8EzF z!zemga4lw}{U&9$>Dj-v0yqIUWwZ>On&htf*=Yh%J)cW5wEwJvPcHEwU>D9zX!+o49LwoHAzso{ zp?kSjg$?**?Pjm(YOFgmAUrl5ZqBt(`4=Xh??w#gKP=GqIG;Ze4U#x?v=6Y% zqg2PFhpt2pJ*aK`;KJV5QRTj*OC#opHwH_da)k3$`tYE(5N@Q|c>h8vpV0@CUpD#5 z%?J4t!$l)Y74v24H{ADU6Le1K9t5pBvWvw29^=klfOn-p7qpcgT5o1Aw{i3g5lJn0 z%l`){%z5sJ0qd9+1UTFG(k`4`RY$P81$CG~{T6;Kpd|tN=9uQ-(SXE^dTnOs4&`uK zNw@7m=t6;;x=S@`t8st6?(spzEVH{Mv%Q;M7TzDn5bO!_y7m3szhV5On4&m= zD|sJJ0>*ozU;Hxkrc%*G%KQdI^B#PvQ`Y>`{3Op?PUXvyj;0ZSIGAAm-#M3Gb7{C~ zh1`jmj6Fl>OV^~{`yOl?wEB;!oI)`w~7VgFX&E76PBnVHk>lu_7fB|YFl4XrH- zge0Tsx;tcM0ErOPbwBMdEAAgeimM5g65QhS3YJEw-qIi@7$3*_4>DthTOd3b@Ner; zWw%?VW-<#+enu|zAIOP0&MOL0z`RH3z3{Oc8ha`XJ&sBZ;5@u6muql3mvp8mQenQH zLvQa4)*dn-Ba@Af;r8o#2$us|oB>D0f~E&3k*ki>0_!pfEDm?9;beW?{TMC#^dgQ_ z^aaOPcg&ULUOrQ%VdqLtysaL-2vtICD+cVSp-IPQzP^8=e&$8RF-b=R#klc0sh_s) zGdg8z4358S4Uf&!$-xT&rJ25Ubjo~C8R1yfc`Iw%66sS$p#kM}*38MuLv5)XApPR1 zYs_Rv?`}nYVZtkP(}GW2!m$?DBThIqaOQZ3OU#d*Ery42nijmMs@7wo0twqpouFfL zGZg_37f`m?-)L8KUV2};;A(&V^Ad^;RNIY_9iTcYld8N<*#v`o$HzoBSNBE=$#}u? zJ2&sf7zL2U*^)hP{>=L6aFPe+ihTLM=ywIa0=JS0boViB)~CE|W}n?=PmU9RF5#aW{>%dpe%#9d{~JJ}mLzD!FH2 z?~8Cu&jBB%6WylmGIIrV7DkfogS6rD(veUAzT$DQYg`n!3if2>75l2 z5FlU|oqvoS1zR%GAIf{mAVz=V$lguC%f{vv5rR2HPE%&$8CjtWdg3s0@->AWe1ZP9 z87XCvOg$`Pfu2X-Zw5i<*mXb(15m$8Vf=Sm|He+HE&+=J$}gB~x6RqNE=rIxvLV>B z6Kzh^U(DIkp>3p(Q0=4)N%g;^yUHcn10-QeK|j|K$C>vFFbmon=g#2`-;=rXbSUB| zcc$b(XKFT1NDO5o+Hv5_$vH$8TBuL}YAxmk}Yg&MX4f4cTUt!7)_y$Xk~+Fe#X zG}`HzU23;Ahiw|sHv$4Ei~Hi7ya71WiAp|3H;z88p_vkobhL~Z*NUI=F*cqafb3n< zQM*{H>kPH%3f$x7fTd0lAFq%$L(mzuPJVmkbOSe`a6w2u+nME2BYD zycy`GMIlhtpc5An;7Ra+CWXtl`b61EOku8BI@J)x=|I`L=TGqYc)KmoHBM8JKh7DdFG%8p%*im5Q6b(yLSY^s&2`?YS$o zqJ}CG`FZx)xFkgpk2;4hW_qX^QM&-8vS&-Ph-_DDz3SckPbK9VjU8 zb#2eNJY|J-v^Nc+d8|24IB5wp0_znej4d_W{=&(2&fTS;u3Ib33OIDRX*M!K!xL%* zkz@Us?-|in#3%#RT%y*5vUTi6*I|LYP@l19j5=y2`Q#`Ibd2?t`g)*+Gyf_(TaMkU z_EE7&kw-UUtDGjClB??Saz|s&90zJ#45$7exLQz0r`^K&mPy3QXjo;8vZTJ9U4->) z2{Up?gFUxZqymU#(CjyT#QS&B5c-5(R3o}^@7}$;K)eHVle1a&Hg$=Asv?Hc^kIC! zGPCGJj;fZdBSyKnYG-f%+fI{wz)_}118{6d@`M!Sw_F*gN-+N|@Jz-tgPO_4TRmoOKjG$kwSNbO5ab^sD``DE`a&0U5Om^PQFC5W1nb=!LR;@Jz*j zAbls#^eP8DXy)o7kYrHqdm8@FZ8>#&L0#4!sj&w|G32ehU60(m1slZy;LNtlOs@t% z+?y&KaI$Fw6beA`512?w&c-x#k+?#E$r=RnW{`dS>za4BX0#1!77rSvKD`)al0(e5 z$8`H`#tNZQ806mrD4&3rXyCrkJnox?ZsfUzKT15OaZfi1khuz5%*=!=l`!w?jYxE> z&fzc%WId*1I&^s93^zkU^4R*gE4lHPc- z{v%5#lM1-5CJk;J1huq)2Z3ep8<~>{vu0stjtj+G@0EXY)>2Vm<$khX`UcYIpxH4o z1a&Mp$9YI3st;x2x!Fw7*;Xh5NaK$KO>Tah5*HEn&nuwzqPc}_YCzm59$WpK*Kj6&KnF@0Qz6$TsgsQLK0bV zaU_rkmx|*x44VYP=-lh+WW&-I07}QU38BG`(beL!c_Ohg{o*m~U+`Dwh5HD`yLp?3 zr#Hym_2}j?z@u7^7v~>Zb8%T!kQI>tOc79wr!IsNK(HYb#rdLvyXDNtKH}po1kN|o zCpu>?1-~eC$t&RC(ZV)88WtTh(FO#%Wj-o7=a%+8=zc_t=W$%z;OIQ2yrGdg;X+pD zAX_)J=%g(*6^K}!+6RX*JHU~N7mD*(ID6r&ne@np2)-27a}p+ z0BjcP-gS^9cw}?~i9j$@3;W9Q&G^Ox^wpKL6U5kAb`viBrCO^h=`Oxekr`B>vELW=jNyS_5zG}hB@ z01`)LegH3b|7gC!Mu4aXQURmVTQIbQ=KrZ z#C+K0op%CTE_2KY=uIA*4`yGqrLmq~B4c1Y6yW0goFtNG%jgO1?qbM0wINnrNao!C z{8XMSwhPaOns@YtR5^vWJ9RcjxrA|}t)udp2M^jr@F;FsRPX=5mB^nGJsIJ3k@-um zpcO$_=7f5+hL?u#*w96raZXF|ih%>q2m2a?n(Yo`hmKgDu1a5$2vk*;Z8NVNDv3Kh z?AL=Oss+FEz}&Sr)Bc~IF44-a)CQ|R^qs`MiE2!!r)W4-;yM4gBVW1wI*D{UU*4{g zZqf9e8EyqIDflwaA7wWcaV5T9iZfnC0gWz7Qa zng3t!`BPjan!$;D#q{yr?nBrQJjss+0xx~*T$)+8L_&`cQZ9)TWla(~oi)G&`oVL8 zoFoWt=i$Ti<-C=z$a;Y;$4Wb*H8asG&QpDD2L6OYF#Wap?>K`Je1)KW8`jGF_v-)o ziD#i=itD7>3yYiVUf5Su?XKAooL2^*^>1XTE05_(Up&@?_tPo;I3a*7ibt!>n;Yqg zcQHzI$@y#_S}=&Cl0&ATmr|Dl*>euh^Twwl&*ALi40cr)LF+spcCr`#pFX=r1qygW z8=(cpj>y;QL;2up1aqlsnNbPJp7VQJ+d_d`A4(u~nlLe!BGB?g9ij1F+?|W_F~BgD zVU-zjCyF?luEV7v(1Azf5Zn0 zyjig%#MHOIb_t9_;oAjGZU5)bQ3F}lvYZrX0hV#3yPTZ&cD88rf)n{(SAs`UbM3ZA zG{!VH<_%;Y$$CEKgzz|QPYrY801dp9<`LuB8mP6s^|O=}i`mYOSDzq$=E`prBD#du~8M zaS1^|arM!)tH71O=9(-Diu)Am&z~9jWo@Fb1Wg>U_v{QOGNG+QM`=V`TA%50-(|i{ ze|_^C#U`QhpW-iGX?Y)`x3-dFFT8%;O^zG(ZJk92H2&%F1F^>iZu;!Bg6CWh$-x~9 z4D|Foh^^wd{F{C&sT*+N$d4-LK~PXI3d16d9+*B4Pd-SPV&7UV5^?wd2{q+aahdUr zz4Y%bO?Kgu<@Xgppl^e(hfr<$mN&r*YX5%YTH?Yd{CPH|)~(6O6a4R`vKM33_uZv} z4KAFYR=w*|9gdW{#P{!+AeXqooD9>y_X%(mUp)UH8207gK&tM3czAyP&EOj1{79ks zU*U$G<+tatG%YRfGeA2H4EUk0{0RyXmm@y*M}B*e;l2K$cXY3f-SzRm!M~GJkve-Q zicn&8G%1Ys^AF%|n&uO>NtxatAbY2so(qFl2i>nXsdxJ3i(>6O{g4M|G09oe?SqxB zx*5354M1 zoNXrtaks7uxW^$ULe{wh_Vr7^V}2)C9CX|KOpVWR%~G^`mLZ5sYaZ69@=M2(>rRSH zHPxtSc^u>{JcJOBuyHfaR(dD*A?MSlOvztX;>nmHFOth_-mG_PKyziM)fv`&dY`O! zJJ&fC9aUD-d4Zw{C5tgOujrJJHs&yP4Dly!rPjRIM;Dbm64fn|AO(ME_I$)TQ4rpC zOoZH&_!ITRe>wZ6_W<2tb_Zt?dH$EH*WXFd;LoluYtFp90c+1swXKOZi zx9zkosMT44^?`p)pk^AA9d_Ox)jiwato}AFfZnca<5J;*Q)kpil{p6D(azAp@`g}^ zAv((2C@y?=bREQbFI3bSZNTaI0g}TqR$(s#hXsAU7q8gSz5L_5SVj%?Qxk))dTjnB zriQi30>12A#lLo5#qqAN4-CSnC{Yp-I+}1P z>+_Z7vW{R13h9SSri#3v8J_R)vM=YE{1?*bb+iNE6?6QDB4&I&JvZXVuIx#JI-Z3s zih4fx#qBK!_HQqZ1~c~u9@eF%iC8BYRGwunR-MfMNfm8TrSor73N9%aZVh`WVVFsl zpRYss1w8MZ;M6{NY~!M`2d2oV+&gkK*oy)45PRLc>Mq9uIc%QL7*Q64NVDCG1;dC- z80+011=vvzRKGQ~iLkK2Bj5e(9$~eNLvno8-(J7r4Rlk~9Ht@`>uWNfHEIphVCWU% z!?1802!d;dtHX>QcU||7@(Z*(3gG22e255rb+M#_wnfW|Gi|;Tyh~h;Z-tqp!9&P$ zxLD+|CzAG(g<4NQhk>;3O&M<)wGhj8LwU^GN7GYybXjIJZssW>?V+H)zJATtAAWzo znNDFBrM2aIT45^+Ac37e?u@e+s*cv}(dOQ{9PF7r&ag*XkQ4z(MQi&b{pDU6xZURQ z{_PTjychc<-CzNab%Q?B`@TLIBR$}NsY=1Z>)3=pRoA9H7u$jkhJB6?w(?7-*?rwi^i%9+>tmu59P2;>TsHKvKxb&y5EKACw5+qS3Pu>5Q~=L zoqhp+y~xj|b_=OW$k?cr-JQ3-Nj-9tDG?TvcRTNd{Y0a+W3y4O#;dT6fN(szvk|EqYM~h; zs=Qpu+EdWq3I!&WU3=87$7=Xk@vFzk1hnOlRkLt^18eF_F_X_(otc{(GRQ6d{8q{M zju?a*YVis;tT8RCok_6Nhb<{^^>R9`1~cC2FRCh5N7qfiyo}LVwV8(a+Y#Kvv^&1X z9cK;EY4B^-oLIg$h+^#-RxdZ@MgE?T!LpsL7cuIL#ORrK>^1G9prQ`1<=^%*Bo@=M z`B+4|R=Qxk&fcUBfNQ6$bk_>TnzwUPfB1V!gP_EX!;D;v@H}^%_KBu&2(jR9Z}#}y z%GuC%jnBqycn?&*tUY>d?wi#ad01%o?->L9%~VrcCorDFtr)w^$7v7aXPviquAAD; z7m-9ED@^c1#hO_u_mc5oQl@EIkL6VIjNNCA4y$RN2hv88Eb=_tf-}i^o%|FOuKbM; z{3}|aADrGE4e%sfpBgw|KarB+FvcA*u-||Pqxu_#w@$6QV3Z;z2W8sfmP`UWBM3qnP*lq2*G72@p1HiWi> z>6(L!^!@i!ZfYA$c7*}BACVpQG3$ldilZw^aNC29vx4XH?6}DeJNeDRuhB8 z#nJ4Avc5htGWPF41zYm|&&V@AW>L#X9uTu}D$8`6t=?y*8r!LABV!X|qAV8cq4#NE z*6-Us$_Whr0D7k|UYHO)dH*#rZ)1hmDF^RDTjFrBvOlXnkVl%+N_KKbqH%RFTd4sA zZM+-21?4nV+^kGWU8+7p<`Cio$Qqvp&{b2LdqW4ogL~p?I!rS<;X#ivu~;^t$Trc30ipeGb2>Gt6)JAnwUXL+rkjl`?fq zHs?!ze4zEC#!V>RrsS@nVh+aCu~P?Kn}N&wuP~P2t-d zs~UXycfyMjYCmkUJlS|TsJ_PFC~R9<*iP{*@-Ix(q3eSG7j2iljY~8SyRRd%*NyAI zXRU}p0C5?81<~sbso=4iW?xAF^NbLT0 zt2I7qMKolK$$uveTVg3pL6K2xR+y-Rkz=9nPt&|O&t~gda+}ZajhT?yh^nwn8}ECm zJ0fZa5IyAXGvjG$-N-QG(r_bW^cUGGbsFNq;ibIL^}O1)KX?PTk=!nJ)Ko*fgw_~>l?gdITL`?ksq?S?dnwv#+BQX`#uZH*N z=Ju`FtO>N$-3cbh5#2TVYWP}az;3HMp1WPUcn^@cf$Y>nzIH7$LOg$*cftp`4pG!_ zr4(B-dbLs`|IQWL(?1P_)tK}4ey&*z+K)JPDD|BVg}_fzo_F{bD>4kbWp-%}L}m^0zp>cdB@bbR z&Yq0-vl*w$PDOUVL_a7xs!@+Q5sONRsI?}(at z3%&{$T?XC^oUG*byhxA*dD|+1i3h+|Eh7P)aGO0Gu!OzUJ0Qe2Nc7FH@rD-3#T_yF zua%wFFBN;a=^+cX$Lu60s@|0Tv3)zcPt5GTN+Kks%7n4wUpM+-sP-a?jPH{2vNENn z8v7;X;+DO+6jQfl?G-~2SqhE6na<>YbNYaO4O%iY8}PJ@P3*q*vR^-G?WDcP;H&WTt+$6xe_03CFCmp+8W92IqJDyX>Sh4qs@{NR8w&C?jnv9;v(6Sm;oR@mu*2h`EB^n)Wcy3*Y^qXS|(R+%+! ziAa9vtk|T(YrU7EPAF;$2zQ-!I!y1&l$5|5)wT)RbYjE>Y~0C+tYQm|Up;~=26~@J zA3wyDawu@=>=yJOQ%&LJ@zsWxHap`s4Z!ljUqYMb^DHvfn3aCuiO*Dt^q`2lJUayK zSI*YAguNomQ$?PZ&*&!fMGK?*oUejMsuGp zZyvm04D{99BhrzL?YN)OQ@qf}UjMlb%VYtKS{;xaM=^%GtBHIw5?cF=_V0K>RJn}h zEo0x)F(#Q|zkfhRj$ZOseO6XN*zOm1ve$p2NfeWmrVIy9;%mXs5sv8TiHG#F18#)W z%bBjc1lB6sXT&yDN?q_>8Q8ap>yFYpUmdta@igWmJ>)B3*c0Nt0jGlw!LG;Eiw!L8%wxnY5&20(Go3O(e8x|$R?K{H6VHMg5`;%P0N1vV>%(Rmtf~VaD ziq53P9jE#vDJV>gxpQ?9lUkjZ?y<|s$d+OIcrC%}>~78xO>Nf;wj^97XiB$-V#zvo zv?Xbwf{X?{N&*O1{juTBOwyFp)TU=k;AGKSNJ4;QUcSlVtD%$G<-JI}Z~f|6{oPJy zq1@k66VMXB9D0B0y3(odS{H4Y;+HKLkSQSR#vg+m*BA!q+QIeBrRgLls4!yb-o^Y< ztr^;yr>@h%ypObd@{7AXI|J)H6XS%^V`oETA3c!$@@sU0Lpt|*o9mC7@e|p7`Z9|a zy|z`phw#R>-_;dfC5HMB(;jg9d-{EjXl)JF;7b8nu9J=EsE=ds+&E@*Pvm=_BxEom zBi#*}u6Zy-S?G^!>gihesBv>xE5ojTtLq(_a3LvRvYgwE+%dz_#rs%$>EIIdFlUL1 z93!nBN3s^r4Vq}qsjD!VydjEBLeHL}1N?vas6SZ2@|Wb4&j@LpIMuhSOO%*5d^#qM z*{w&?z&33%#Au`_C@R!kR@=T~gri))B9z$SPHR^K5Xw-jWGQ$RM%8h2Dj+>p(-`FjGLiAIfp{~e1EFUbmCA^1zKp> z_fRU>iQQ#QKs!it2K*;KS)3Tdg(9|^$sgl)gzaDCSN*-_PoC<;)_N23TY1*@CAV)T zsHA8Vy?C}mk}k*w6;#XxYq4e2C=zgAY`?!O63v}M0^(uWpWedD77uwiU+8X9e2S|p zh2^cj>DQxb3;wVkcv%04WZ0c5U~^_X?`Yb8*|anq6-|Y(*&5rZ_+3K7D})h#xsX65 z@AbAnvxsw~48ka895G~G(2(^+0Q5>FXVbW~ZQx|eA3;fb_*}6IdT;Qa6zQU=IkAm? zh|bMM^YCX5OhxL)D@v0wi2B^imj)#IQ>`zKBDoko)qO7k`lu)tdFC_s6Bn6KO#<`H z+*IMmp?f!+%#iYL6Rewm5U5f_Y-nO#R&&Cast!?U-}o@G?wCDT|k zixmFiIgj}h7%2FS%ONFeAsL)Cu4n50%dI@P#tPw&r_s{L*BMDdxXmzVekdY+0Y@s( z8FB=3%w%NbyN%cd}GUo8T3gHh~cop zVy{%dF?^>*g3~@fI|g#MT(|Z=c^GSG5G^8UO|u#JM1D$Qm>C=%WxJA%e>VDkpAZ5) z)F`6-Y2qO1L^srw-2!k)G`j-#0mRl>texEiUk8Sk1il_e&J7J}uiSj&c;>y^c{3kf zwBQ%i=n+9Wfd#{qDZN#jYx`?VDSP)oOojcDysB`X8l0s$0uCGGamP>w2wi z8S<*yU|19c!bI@JDpqf2v+|AYv?mSs$oKs%>3_DP2#FPM17zS{t)9a(2bCS|NV0Y* z^2Bso;$GZcawV+Y9Zb#Uz0s~vZ1;f!r9x>IpkV)w$I`5xHY(t=U2kKoDyzzIR<%41 z_bdd=9C-GP_vT<2ZElh>u>F+EjzLC#e-*W}r9H-Uv0}eP(DuNh=SR6}FK#L~d@XlE zhPxm3-=l3vn;t$)39dvtY-^^@ugFD@92-rk)a+vemtmB0y}MFdDb2(CKDgE+E-{n- zFo99<{)Wi|;Z=FR=OIk0$55rO}kUsP5f< zWE@+n?zWrL{ORm3g%`OtDy1b{4p*c>Q>dlKaxW@RTcrYQt+RhU@Y>YNICgfc#kbH3 zhny5=bSSUV9T2>?{?3=^+u_h<7W=0rINKlVpwUouTx7)64$)$5q17v`t;mmG%k2oB z!2`(TWvgqEp&JqD1m7S_Ee*z+P=Tz?%q=qa_UACNd zG-H+GJ0Z5F{D7SB!B}j@WQrn-nONh!^hn-aAsPR$IsS;s_-G>l@5vXSx9%{%--BU= zT(ukHNZxj2W45gy_dFO2qqADcAioIoE5h{xgX6Z@_W@+TUVicjzibC z%!!?+ESQy)$VgH)gpE3S4lLdb?JwvpykDa565$-O;L4xQ2gmUtUJfUilm!IINWEV5 ziit{WmCxc2v|)Z=tZ`S|Z85<*TOdOg;gnBnpN`XIX%2dlmsfBcQES_;d8t6>_|*z1 z&vTpLx3R1xpTa%FFrc;UF?a-4C&rp96}33%K7 zA3~JaPJyX{fXE*N>^d@)ldD53?QmyD({zxErT(Bk&~9&`b{9R-o*&e5pk*?z#vQy-k8>jQxH|Lq>3~V-U zGaDp1jme!%(c265j)F4HiWuLvK*#^_GNlh3ujb19O}B1{TMH-_ob?|F=_20TQ-G)m z>@Cvnl5#BvGOm{=zG1J5yH}RPRH-8h-lf|cnxMduM+FV9+@PY7ibK7Bmf1m!(6V4` zJbWWd^*LGA?UXlHWbVcMt%L+A?J)PP6H+1xrCxKax8Ct{h6UrRK^=3F8=c##;*V5{jrDG8q%71t!_PT@q29=w!fR1 z=d5^p`EO1Vl{^Z&bo(DcZ!pG$x})BgB%;vfSA?KkLaN|6QWfY;E1ZGs!y>YK)QDGaz=%3hF;keD<&yyw_vw`8IY)i1Px?^ zWM}75ypL6dFJ~rA3;1xK1qTeBkap2vm=N?q<*!o_U3Im3#=U-Of2gaMoy7A)bI#W{ z>=>kQhg*11#PwUMjGcGxJVM0T?)^=^fEh^Zl;Tye_X7`7VcSa=kODIK<+Wvn~oe30OW77ubTjFvR9Y-*j9@DIoA7! zeb0A9+Z=Ys$JIUe--u?C>Z4+iDJg{_m0VYGeYds8rt zs-*x`?T}F`6dkh+W$>T2pIP&F4)$tS)>E7~65W3D4IQ>NBa63amUnI*6gq!@a_67B z50&_YQGqC~$!Jxla!;-5dCPZlB1*vhvpjqz?W z38t*Bv2*Y0iSn)7Sr{Yxd`?`*-su19b5LMVeen+4ZH|qbIf)Jql?gd04J31LK4tt! zk{TB;Iim$Hp#QXp_6Zq&-1Uxw{`PB8!oPxq^ z^qYwUc4`5A2Opj`7KLD-SnnWw#+H^y#`#w8ft{Rcj7*eO58garFQ0c}UnZCUj`KT# zxxD4xdtHTA2|-21&kAp2s+ccOR2AoFBYKk;(3?UEhMJZ&-bc1Yz1}Ka!-8I-7b))D za53!pXmp_ZP1uKnf`UPk??vTu2pQHkX8}Go1aHb!H>lX(?xhE zdnoT|l&)h3ihs!JSrGU-qNP6l0~qGK-GhNu)9RH z$RHW+Bb6qW>o-!TWlaOJb_C*&0hc}AKwsaUi7DHD{V1$d^d(;?7%; zmWi`G_fCZ7NBWWdd*>eb3;r8Lm(M@^*L3Rt&#MI*i;IgN+@w11(zl1j|1VS{{Ga#n zzhX_r|8_6B#Y$OYKlQ?Z1%V&fW`(K%1qoni{(eoL)8G+)0eZ6+L}8Xjz{_NMYuE$rvo8C;Cf0TYO24mL6)Q1Gw~5 zLT!Z!GGEK{P;h* zLE<*>s&T-`PuDr-KUY?k0X^Ur)RJ^I6XDa%sv2SXY@WN+6WeY1Z@o-Y_AMj2+aY^J zW!KGw40+G&FP7$SJe#up{v)i$bDK@Gd@tt`(Qb9in!$|j{14x!?o*K4|veE6M^vbxbO6KAJj@{EyEP^0uZ$vj=v05&M+t+O_zG za?8&V5vm{JrdAqOg|pHO`E)hjylGlS!c_ZA%O1$IwGWbJto(?ng9dG8?o@4e9!yn6 ze2UZ6DKVSY{n4d}?ft6cv80l|JeaH7msWXyy1jj~e{z*g#!s)prk!`XHV}qg8O-K8 z@#fJGw(1acn!4OOy>}0=#jRsmWFOe|rKF8~x<=eMuPyq9|L`R<`VNnVW_AmWZFe8m z0IuW0Z&3X9gRZpuX_FR=5V1Uzt?IF|pD+&B)mZqUY`aFH{RD%-=B`ntV&Gb-z;D&M zqf&T8MGXjB7y&ztl*B)OHVmp>+@|yK6`2<*w`fRZ5XywsH3?eRn9bBVxn&Y7%xfpfnLtbwZ(F~{L8#xnaGoh^ z-kfij`mX=ZIF1ce@RD@3CYZ=z-t3XGIoqn6m&d6k24>dP)#cZ_pG%b%fXr2>ED4w_ zvq6*77rUd>_1=DZuA-98+ZjR28sFE^;UV-ls$}M>P1_qp)eN7+lP6kqN{vzo ztg=Y!7-mi3Tr2yDSLZP_vFhtPz}g2g|p=O_JE$iv3U~FZs&u)0m8_f z->Tq~Ux4cNOhHl9D7m0-aJ*|rWsjx(xyo}jy%7l+@AWbk`G5&>eEh~UgL;TWWb)eg zMCuih``L2dlFypnEN}uNVwy=huNiA{xou>q2HPWS`8Ku@31(t%_2;6nfPG$212(3+ zQ}2c=IU^?>Y)rwAy(I>Ga5Kgh4bBP6OX39@TE)D;1_?BgJ-RPNzk76~Ryp5phv~R< z$LkEG3(7?oH6o^PrGc!xiK>RYc=ysVX z{(Aa%T;~|u6KPNgp`p6jEZpk56+h`Rqp|QItgNH;{F&ZIT|=n%CUB(zJP(b#&|Kq? z#ye=)i>r0uX*HE3;$;^rT3aI+o%x~jPsZ{CeW#mWr)7kzyvkfYSw;%vpPz2m_~mHwx5qjGc|d!`}3@W_pOTpTTlO8IqzSRxjF&N>Hbt9 zOWUamYmYzQE{I)oP(&D)@){$I5@zb1V1FP=FS@fo$e-5(KBaCNQ$?A8Q2juQcG$iY zkHw#ncNIFVnmxwK%*7mM>Pj*Mz@ZOc()(U~`0o#{R$ZC+d{3mW#;)HMH&$2@l8~<& zm)Re7{a%4}t51<*m7aMw>$`trFA%d-qq%uARrxbR1_WXuXwzl}EM*Wxsf@?++mf2~ zg$0iJ`T0appwG_EPIF;In`NVG5$)FIbgdgM!0Z{&y4{~|??qkbn&g5_rQLPAU!N}+ z++7R?^=Bm}2IEWyE*CFev?UVo_RS4bYBbpx1#WJj8n8pqVJPeIlwqDBT*sj4?MtFE zarh>)(2BT!!E+T(=-15eLJ3Y1Uw-AfVNo0)9;k* z>HVR!@^2=;It=IRqD;X%lh4Ax;6#8B;&oU`OPD2{18G2lTBkCH3gI*yC(3YcigllBeaM6dP`^05P*P5qhO zY=ev4)?71{@aiE3Dn)YDOnGRR`A?6FPFdyG!I&k;$d~seo3dlU^PNyAzhy&N^Vw&S zj`%5?K<%xTjSB8YW$+=HxGfbo)wABRCVK`1=dm>>mD8+7WZe94{9oT-cuO- zsnrJ$X=pSlx7b}Tt;|fKaL0*yPsdg!#BWSh_FKBO+ocwADPV#R(KWAoVvX40vyAlT z8~J@!X&vQSH%y1u^+c-CV>5}(-!@!A#>OsJS?=CVUcZo9M_sz$JXWAyGzJrM(D5Yx zu6~W(sO*#^nMr6t{u$2|vmc0O5VH84+vzyYD`p&L0+Bdc_qclFpBI@@X(C{G0 z0aTul^WvW;epMOlrlDmwV-F?_b-PPjHg=j;a{`zDd=K4S%a6+>od6xs$dwPaMYnht zwFL9sFD`m&X7*8#(BVZ!m+oL&7Ka1{@ynbvKM#uapU-}b*7az zV^uGe=%^(&i+&~Z8)_YRl24Bb)QIqT0)7_mC)YvTlA}vE26Ckk7>uGQgt2vD3K+ee z3ne#GW%%zdKfVyWzcy;$8gh)iN9SXlE$h8JAGj@XVcg`HbhH%5<2K*M+cs0gI5rw* zSr5V12{Tnmxz4@0x;Qpv%JgFcRm!J8+E0pRl6Z*f!TCHN$Jkzu2-nE9K`%%4u&2-} zoIdS1UEH&5kuSFpPm--iIQ-tNH&>Y!<+0m!OA_0Z7<-sMwbR4Sn~4=sU$Am$=n=81 zd!#f8aHcsM@h0mQFMg#dbAeAxpT<9z20MBqnC+OK`7Qhi<1NEwRnF`ii&%V}u&;2M zEPG6^%Z#60H0Il+#AeQ=$xpqmQx;{>MIc{Kv2^^OwZMZ zNqPrlN#aw|JK7=mnfjt1CnHL<(@KajJZj|!Fi*ugRi8gk@2d$1TdElwmzqetM~`O7 zcn)-ode`8UL_|dB7VGl@;9v4GqmfA=sjPCoy(5%t$*W*L{U_~hRu)6Rl(y|UIT-t@ zq{`2}_p39dgS6s=%Wxg=bDU>WwfzAlPE>dTvhM<5u3 z+|}NRsw+mbFSL4MS;a22xT0IAgq06J6P&c8D=RC@Y}#c}DX&}>E!zW5=slgkjz4ab zop+rD&v=tjs$bPq2Ct29j8irn6l&~jX|6StMFB`LTP|Nt zNP*CspD1T4oR(JWxTNBLh^{vH>S3pO8Rbs*OI@P#;ragZ&TF~vw5XzQU`iu8{9!p^ z`s96hcz7oH>&#s#j#$*svZ2Rs;49LOV$Lr1r$4TX{A?Yb#2n~6)zkikDt)$cLBy649Tkd}vr^ou7G3%7tdl4xACUA&g#_c7y!P z+=)s{OBMO!k1Xzfmu6DMd^Rmt4kNbMUxk3{*THaXmUM_!P9TBNapFtPkqP2HezqZX zn|$b!H~2`0beJ0*u);qMu7B1pD|#B)gY8V>OF|2uwK8Q+#oeHl(#|{x*p>&;1#M0! z6ltXCuogt+R(fsywkguexwGvYPsngt$CxpkH5M9(oRbZMD)(8c*tUN1S_=rdh ztY&*kLe4mj)j(lW%#C*bqd(^+XbIZ|wrl>4I*T@%Il`#JuF{L^IezA58zm z^JTD*Vo%02MKZ`4vV9dXE`K?**Siy9-<1Z7u;RP~WNXS}B$;jMAt`l^!D=$F-DIEKa8j_e&fP+{!Y~J* zulW}@{^-Y!U%<(KB3c`$8qBx)Rx(EZPSC;Td#=s%7Z>y9JqtY%4LMca9goBGZ-H8| zF;er1Su()|QD8eFT}bY3YunLctwFV-ap!8 zmp)42=fPPA37~1xBGxTFNfz}R$W#AX+x@jwt=7{Ehd15^Y-a2^48AZdw`{z9W#7h44>h4#n^h%j%?enz9>(ir z%k@u|TVnI$Kue-Q%-{Ko&Y*bhN5)rUj5Z$&N!S3bJUox-ZRS{`CnhdZnGp%3+ zWEx*3>3dHsTS^+3mFs{>JRVb|m6G)s-!12h6F#JE*X=l3*Yw{QY~1W`4v-kRj9L-0 z)F4W_WZQUcw!X&x=)S&7WEcC1>8x(XXXpSh+pfl8n~hOsm*1bz{F`u;4`vZEEIQ?cp6$hXZH{~|^qzQE!r?sd zeU0C6-~L7{(9N~kJ@w4DCCnYGhm25Oq3nmbwQ)8F2LW;kkHN#Dj2?hH-#?^Tb3=^B zT1~S709ay8-P2|Fcr_PlZ^mxG8sF=lJY6>upFTGs&U7UH2ImB+wWldq>WTAZvYT2- z(v^RFo&o~0)K#%7#B(tGccCkr4bF9GLm6H^`p_|kNkWUKK`TR)(6jbOsU^#I-d_~} zARvCzimW!mj8oAEoEotkQg2Y&*3!nQ9F^{GCGp46PK?X(@5GBT4g)x1l>d!*nF6ra z>jZ0fUw%bHY!M0=r9cc8!oLKzR6xxZB&Q<80~@Rp&%RCAHJ;TWr)FH?+A0X zA~Q+|2)ZT3sKUscVF>2*0pc+PZo98}Pb3jQpTL(t_v!rh6IAWaazGw0&Yr=)AYS5j z7Pzuf=UVKT>5L4=PNjIlK}Yc*iiaRi!zR6#GCoIYZ4ORhk6qP_J5Nt){Q?5}#qx8! zso%@emR~hSyISheG_!$TWyR^b!tJ7&4x;Puzimz*p8T>5(U{t4Oh52j$HB8F^Ig#a z028In00pL&{GF_@<{Jgoo?xI@3%z(MbVEF1cV&nxn22T-DQ>9&0m2l)q13~q!fN_5 zor`|Ty!ePCcw`?@45`xS1Gu7L{gd)rvHv{&VRa{eFMDQxV@d>x#XZS)Uu9|sFzg~o zN2%OIpVhHy{7>2pew@VYyKG-bZ`uB{FvX~yTNU;|PSPbc{TrL<`-@k!9!>QFBy~N{ zWws%MuY$^q$Fk8mx-ow5-jL=Wlz@)$?~g3MeZOTnmKq3!P<5J=&+M+&Ko`*!&{Ox} zFW3Qk^m}XKt0GFb?V~}3eJ#ruf3SXyeESzlP(wnN4Qk(mjtKleJH^_i%(rub;X|+3+ysUIOcEp-=gYL4n@sGs;X!mzN*b zE3#XYZXhTRwTW9Bsb6ChstY^N?9jp6j zjNgF3ELMCNSXi79)x+|{vUaBo0u;5KZ(-hzj{tgn{jr0Y{Ml*k^Lw|LSU3~4^x7F% zK*f61oXW1pl7)Qu#*#~t*>X^wUvcbde-gc)OT#W6@AjW|Yc9hhG5ygvFuy1DaF>B#X!r-aN7RPz}k7>llG+^5Ho}! zJeCC%U<8bEhU-Qd$_Rgk=-1U$HV&;bF{o8kGnUP)kUX}owT}VVLC^l#L`XH$CVTL{ z{&irYd^84pkzcuV8kCi=;5^i`c{taclr!Eh z8R7aT<>(Th-3kF9fg9u7Y%?d?M85SGPi15J0XRI~HZS2cDQ;|GT_M0>+*NM!s$Rj) zBiO)Ais?_i!lpxRFt1ztL8>Eqs>V)}QOrI%n3{3U<{Cm6VNu`WY}GkG5P@p&c1lY# zL{=sq$4@sp__h(fs53DkMCD9z7Y+c^BllgjuHzd^^Euow_13~xEf2z|xk1lceg08^ zZk63=KYJzyaaRU|J^3x#nx5>AT-sgD)wH7aX(_Xia6-nSTeYBneq6aOgJ!=~)puHE z!-b=Ez@`^XGfS4~j^0;etOw@Ww;AUJ{ZNF@)L2Kj?86Fjhx_Z(tylc0FV8NCeW(~h zmAn)yr$?2k5ijdzN$==jXw0wf6}PNyrR!jp<{3A{L6JG_qs^jFGENnKWwnnU9VsV0 zbo!MkOV(>$P{6bz{$S%Z-}}iG1UPuFK4_Ohpsm4mIEpcP* z5zDmAXF&6e2wrfHn=6Pnqu@&dGg?mMyjM$I(yMg|yGf{bj?U zYtxP?EZcdXO1jNGAf2Fv04lEwua7luv{5o|a4xwK_~$~2O*>-z?n9hi?+8(w0$|&j zo0vvji+Wd~DJ9q5#VdQ*F|4M2z&|j)Tow+fur)Uq0EuE*<<#xKU>Wztj{_M}rcrl= zIYGpIcmMJeG+LKz)nF7tcFzOkC(BH6S3!qioA~KkpyIK8N#TThnl_;PR_DP`py;ck z*Y?y|!q$ONt?*Vs>$*qZ>8Kc+&(q<=`I+&nl?Nz(Ygw8VrO z$~PvBo@jq{-jK8Q({|^Q{Y_MV2soq0&TewEd3V|&w>Jvgj~z>Sy0cf#z1<;av&HJ? z7G6dyt?YRC?EAACdpv}$N%Vuy*x42_*LG}Jp?83B9J}GxnQ-dAtHA|@`mZD51vR<3 zBxB8FL4MDbp)_3ul9`Dhm_sv7I6;(06Zz$tlI_v!IL%Scin!!b?bjVAyOyKL z^&Hh)o+yN2QH$X&OCK`>u&OZr;&YHpV<-nVy4)-Oq5@EKekVmGC2chn?#h7=&DVfD za2Micdcne^q|n#$5|I?6IsX5Mf7DG(Mm)S_8d)Qd>lLwsP*xb>7Y+QQ(VY656M&3U z*goosFPU=rF7dUf%{`R=0=+NizkXl5`eOJ{f#L(;g6MxPAg_IWI`)s`4d6%rQ|sz~ z>23WlZisR)kzv6=4;8MLo!ezK2zYKl;_K_ScmDNSea`(mz-#?a^#9-Z$p7~|)+aJ7 z{8GMKR)9_SHlPg-hc6GpOixemDRmzH_ga9{0=xdym6lSIGBb|P3_^+TFHvfgThxyQ zlg?$Xh6MnNrT`jx5pg#`7RdO-ou!`sk*`WJ02Zcrcj*J-`nkXk$i0tr+uWDJv!vaB zT>cH96YwrRe*8FB%$uA`^LPf{s1Rt6sdn!6>?%T7dyWNj!k$9(&KovhEEB z4YOo2i;M@*RO`5PXNWaG3;7P#QDP{+$dbN@@bJE&Y`Maq<6T11{qyz#)RuX3Zf>q1 zm`JGc!COpsLObW${KsvCYMB%vCu6ULZlXq)S>$}QQiZJ{!oZV}h_h*M&M4_~s&93g zsu*kY-C!6aa@Knhj*3k@Ap-OC|bI zBM*v_0Du@^Z-(|IaH(=Xc`|2Y%Paxmj$xMavFQ2s?WzA6>2MHF|Ip}B|HRbp&Q1c8 z1Vjx`tGNOGtkXZq+tWe9PI5KXAJ;aHoy^P!@mi$^PTn6}7`s*R$`TlsufAih$+F3yiNs za2zWTiVi0iS7G4cR#8;Q&Z6@8oFx&alOft|GzT9#`*=X_XP!t|lzk>RlZG?C9~0Jc#>#S;MA0STm*)|-iUaFmn_ zXPe*7;@$S9o9_xZ)JlD4vP@wyIdZ-_=lbrr)kKl!>JTrtW7R+sUtDv=r6)YAK>qRm zbyO=guPveqIkE6u8F>6%m}7?Bm#{evNPzTocYth%d@!BsaZ34nF1#P&UW>;&7NhLP z05DZf6^sPzfCB%`(R@|yZ>adn_vVcXpf^XP)4gH9-V5e+-XNp?952Nw^v$;iF13Hi zxiPYopa2c=d{U=Sbu)Y!k9W~|@EcKf$20LIAQr5E7B)7VT*tpu5CN&x$^Nep=!-ZOL$X_3V>ygbfKWczv-|D+C3|*Aycf$)HC?thw@^jx z7VSO-+OVZ?qn-ozGLPBV%B2>ku%k}E8U|7TR)x?`@Mf;Bp#l1H|GJ4$eL&&8@^!~T zF`+vfBE`nvn(7vRcS50Xa>mS|oS^`j7Hz|I(a_sqf^g{g;@T>W0~5GmQx z9$5|!7#S%RkN*WyX_%VKZ4pV#-zesztvjwHyhH^TJTrD19}RNpOXlYq1|4QO%9eRS zp8#SKRfmcq@q9V=y-Ct3DW^XOY_{3DYyPkp(y8ncQu z{*j7KE4^C6x4xRjxd0{B&i(w7w}H5`Zl*XgnzwAw$(w>FICQm}KFS*XF%p;yH?aQ#`)XjWjlL*+`pwn)45+gyXTXUXMm`{(ocd zJ%gI+`o7U9ii#pCVxfrTqKilgRX|V#=@J6cMS39gfPjF4z$HqP-g`+Xq4%O9Aiah_ zLO^;K1VRrvi|fAcGiTUmD*zWAM*82Z{ZO?>8gy=*s524NWrxBYb z-s}i*yiAi*;ub+3qxM=Z?priCvyr)X6W&@>k$M9)>gZ=hWDlihwWF~1SXT1t~9Xb(k*-f=4^s{KCv7hY#FL?!VL zB3hjPK)V?udT;oXXeMs1RWk0&#nLRw>qv|aVOZl#cE{ODGgmP8CY}f#=(wg`v1EUq zy4e5p=J5sOq}(b}VB!C<=x)<3?JjQt?c92Sqsd_^=(>Owm0S-Ee1?=<5)Urqf468| zTcjgXaW~ghl35WjriFLlth{c3eG7I^U1g9{>_*tm#~Fy-dl6jU%@x>?#_pHi2gGD9tv?%I88b$#mqor4>;uwyOIZGMq?>+zC` zvt3L7sFUJD2fv$h@;5ENdPJDe_Ky!4FH-wZu~?vE$YukZWcS(qCZekzjC{wQ>^^v{ z99$vm@A?+5NpvmG^1~}R%i7U~HKpjoMb^wpnfOY4pNC4cePVw3-GX~}+mJf8=fAXv zIOqF*meeiS-U*Rfj}pD7{`bX=thF<2PWV4$SC;94#um>&6?!k|xvtRXFD%89) zw*KhfIRr3#_p*fgdB#{BKNN;3Sy+4i3eFNk7}8B-NtJMERrN4SJZfb+Ug3z+s3mQf z#Sk5Odl{Eg!h8t{Tly;-F!f+sNHs+2=TDLW^ZIf70GBf&nSix#vJYz%6ItNeQY=h2 zt};0|v243AU4br5G3@hBlN)!M$oU{jt)%bDT0X9|v(9v1v|-DfbNjX>viP()@?t`! zwMSJ+sC*-qSU4NyneN$qA&#?JQaYSgoj$GB%yd}(6`vVk(BtcS1-VsK&D(ib=-|GY-YHZHLmB1h!<$w{hoAc0TdJ# zl}1l~S36CsPRxRPr2i@eY#k0C<_+%wC8sIx0FTY^aT{^F@jf(+*O9L~SqiW1 zQNd8s4i&z~Is^#5Pw}7D!JcvJ#Xmy2U)OH`j}@1o zOm6@5cRkPK<%lacJqDNZ=^#oUf`aNm;L_i@L8#vT4Js|f-nXimnn*kWsb zLH^QcPBpG?c=UtV=}A4>^`(L6`QxY)V-Nn`d)_BZd}{+9X9ZdOz56;PGgNwrxwOuq zuZz^3JWjbplM|5ATlwCNn_%aqoH7$eikfOCy+1&1+m;gMrKk?zhp9~x>c7?n{&&3- zSJ1u9r%#{$eE%e9W+33x9F#;NwX|{NM%_2kr<>_qE5Fqi#uoXN@4Z%`KI+d_*I2W; zLGMYAP38XAEkZL;^;Ag?Jl5p;gmScy`5wfP%jM7)mamR%Pv+CDZvf&*PGXhaH9!NS zb+L`v+r!<7VzWOdo3C8E$pfbx4wSP}e3@_FtG!oX-2E)%C*4_0Jon+8n%?zd{m@&o4}j`Uh$`NjA}at)`myRpbt=&v(JkHG z-35KEA&KVHZ{AuZr6z$#&2^WrJvi82`I4a!tXwkH(v3_9O397XrWQQ**tJP>hM_%v zFc;Jq;V0~<4x$d@e~HOEC=o@GQw={Oz-8ItiOg0ic^z{ebNRAZH}zXz9}Aieb4-uq zbe3LcdZ@%mt7OcD8H=fj%!@YpcI)g=*)yv&cU=88Nk*ToT0n*Gz3!im>8g zqDaLVoZ)C0P~1r02aV9;zW#A)5dznwg)u+3IV;!n)Uew1g?jQMo@k7AH0${I;=U)u zF?X={4-lj&M_P7IdAHIY)^%vJmVK$P#kYTJKGV{RO?cx~mxw4ilEqQQAn%Ch<;c_( z(ivAKi|?&}xEg{kDWx+i-`?U?2p*`*(7LMadcDq;2;V8ILVc=rYY8l%ds&ipn=Sj( zfWhr@%_rvQUG21NSkJ;;`o^|>d z%+)S8NZk3RLqYauWHpkQ$5=hEvp559aUmv*Z2zQx8= zxCBL9Jr|$j*%$Lct>foUtygJH2s}EI?oPU7+*_rl`$m|qEmh=1sUCm^H37*qJL&Wk z2&d?8VLxs<3MpgnVsKZGSEQ{Tgz-`o(dZ#qYJ&_!p8; zu7}Ilm9_DSW)+){u^$ zUB&ZZQ}#tbSoy4`1{~GJ{*;F2i1E?@KN^}fBL<+Txt~U2vG_ZI4J!wbiwnnwm=GKkxm6RvMv1o9%;9I~quo&MEkrvvg-QSL15+ zYYxS9H7R8L#MLGqF5hzO;oLY zJweYbuGyUct!m26$z5*VnSi^wRo=aOx58`fmV!=bU-9xYZUHI9_5%h?+HKv%MH^*x z^%sqFQK_-mRNsV5T6&f`zonF{24-qMb){YKQT-RU)zLysKirjEJV|%)Y%#7YM>QE? zg1y3&-EldBQ0fusV^ph+53L_JR*KUc&-Y?j{TrF)#7@xwT*x5Y{|sM|6KwBbKZ-cb z0GU_8E#ac0qtUzce}Y3oDmXs39;VLmaQ~$wC`c~1*?fj4Fy!VeWHPi7SUz38+)rro z#Xj=g9{S0m$aBBkk0LAM)uW319EL~TD}p`KiILf#F{?llhquw1NglG)tPda5Fk0Ec zetv$q+ePoCxw;a?OaRoVGrJzVZ8^UcrA^=H>=^Ob`w^o!An;(PX=yR?0gkJL=saFH z#&$j2cBN)sbHlVw+C##F(BzOx-{vvi$OXS6I{9asp^cYgw)pP7OxJ}zIjrA z%+oHhYHcJvPQT*nO{PNWI(M6%6}?)IE&if+KAfEEzT~}mLgT&ulwR0kTl zCzE0m87sM=P=zN&4Z|^RiJKMZ4r0BBi>_w!A<4Wj%p^DOZzT>c_AjZasn5n7GreUY zQD9X%^N{OjTHmEr**6R2EYr(t`LYv-TwaR0ZNJMC&VK;vt9*CtxEo>XrtlUn>v_?( z#Dwob^2OYo#ZalWaxPsM@BVmPRfXp)Z-v8zdc*$q@cu42*mgh_NS-P?l{&lbmm0gA z__O!p+;->=zx$YlKbj|mZFAc|zur2c*2JgAb{ryWLeC;Npu?_CXwnd9+BM-|7yni3 zW&eOvl{^8kmr)>Ja|wJs&HVf>@Zr?Oe;sXnqNtWCXcTKWpsj=?E`As zkI&$A^tchi=x8xrfzkc;&LGn^Nae5|)3kXL`#0;_E zaEk_`=$iIB_-gGYWDFvIjxCT<%QX_|L~VYh@!6d-S64TVu!!s3r-)4;cE%fODZ@lN z*$X+YxKsG{iMN?A#7HX%__rx?{zb(^I|OIL!FD2{*xsMSVD)-$NV& za$oHdWqy;|7i__|vRA2=IJ}*MeCC;O@@;c;TpUPY5UFUGxl-pk*!Tv3Y_*FyFM^SM zUs2N@f4*=mkute_`5+yS{4h`6bUZojk?X*x-8~jkw0>YTM9(tJCnaoP%EXJn_awXN zzw>=3G@yl4RABnvTp?(hnIM9gs-~{|4&jk6brAQO%9GYk^-gxx{V~TkI zMH0GryWleAuRXu77So2wp+vDwUV8;A1PM#aS2=i?GC z2GcJXEG{?9JXH*ffSUrSU)ERZnx<^(mv7!$j z*;p)wIN$5i=1^7@7RIZ67Pc`tIHCE#Tp20GWv6Y|;Hj)8W5*u2sMNpZ>9C)6HedUB z`Zu}CR9Y9BuOgFZ9ezSg3bV>J0WC-CV(HifZ}WuC4?&97Buq3Y0cTIj;ZAfV>%#bX z9*VCQh_-%cTUoh4c{uB(o2U)jkB^MZZ06zF*dUi=7qywN#_iGNUyasQ_N*;bhBxSw z?{=bZTq}BVX8U>#i|-?t=hEt;URK#fmmosaQNn=sCa7I0M(}=coh|Gy2A(>d??c+( z-^;B*jO2Y3ZFSHh)e2%*FFR>jDFaQ!4-J{h?&lahhM!%)A$~6W^yxi?xw>){uELiU z@}LAM7vt{@)U!h92TSY5UuluIH0Z8rJ-i=kBeo9~05k%}99*OWzh$i0doNe=3{%_K z4d{IP_r%^^($Ua*!CE380q>Lrwj~ef&CgI$7^v=X<=zKC#MiQ zVU|IkVUYtt3M|ft+0BlGsf;Zz+l+VFdH^1>ci* z2di#wd?gas3ZzitjhS4)_LC&b`7)kbRzkmIyExF|CSFRFxvG$n6f_9fg^WdOL^u5fjpHykB6^WT2ymg$0 z3E}!Bud9(Pw&RgW&M!MPxG&2vx|9vo@BG+&?WU8niv89$;eDqsN$h~UhAlFxOL9(; zM7)3B@6ytSph=jqk{ zMA836$D}!NwC|594#xxY)toq@VPYXZj%?gL%xa`pZ~IV`_w#J_Ep^=f?i+m4+@XbM zwLsBRoa;zzWUPQMb%Ho_ONo#>G$cZla!CKKr2%`b$2zDpa(mQ({u6(HB#NZLvOZvZ+2j_o{hO38i3$yrbSc|Rbl~K9RNzN&U&(7# zV_uGqH6AX_Zdw{pk&1a!KM)v_nCC+=A&=%VBE!DPz5k|P+x9UA)iO~}eH$2HJ!wpK%k#}hjV!QlgJ7EH^@&5fc1q?Ng5s^CXJyfU5 zUT9FJUpC*RWo;ezY)rfZRT_eZDY}n2)itQdn9aFbSXfwt1T~}Pp18lpSZpkq z+=|^xyCs300x7l|pkIL~?9ZeY(46@q;@v7DH9@{tBp}p-P|{30r14$5+TPp@larI% z*yfS^`3UmsPgi7C1S_l2wz zG}x%#k~=?!3ym$>zBNt-f2(A?3!AOdT^rXgIn>S0$z?i-KcrBYZrso|*H24LEgT?# z8CxoFr+!a1U%6swiG;KnQlqSKcq1{ta5~6i33;3e zR*u-7?Qgl7tnhd?%)*h&^(?c|$^~VpK9xCwQBRLb< z6m)jKomp$|#p~XCHm+&znb{iBIU@sk2a}K~xS~a^_VYW*Z(RR0)mzedLj6eHLqZ~( za_a znOINTp5ee9sTlOxftXVkEmE>EY$E*pVNU_c}ncI|Zu3AoHe4r^pyR zpW{^@kz|WAQ~w5j=LXwYTpS3rY*fn7FaDvfFclwiqqxDWgUFr-2D7oURvIpr)HWq2Sx9Yjyc7&3t$Y+{ER4FFS)|V2+w>* zU-dwk{~ochlLjKL=$MvjRvEBaEI-!#A1weqB+BTw%tTVy*!0u#|M8IgZ^!Ka@&;4g zlj0RS3?0^E7655vWa!@$&# zw}i`2#gmzjX^o7`O!BaS!{{n^doBB$UneM=snkbn#N!uQq}koei+KlhEEsL{Yj!6m zC(qr(q)b*=Zld3F^ zM+Ud_SNFEt`rNf3vVAlAQA_LV1;#>^74M(NZ>&ctxue{^o=raa$P0B zsgyNeR5QI;wM?#QjIis1W*vX#vyg$qz0KazpG;Q`skpVxqP}=#REPDj_I9>3Q$G7Cyt8vz(GBbHR(7;+Yr}CV}m1zA0lmapB z#-&TIa!Jme{QCzJ)FN<#y*i1~5$7Cp3T>^e^SoPJwYPrxGw@1D8Tk14Y>>$ngWX=< zZ{G%!p27TToTi@vI1&$@moLfNKz>iO0AQpFV6#dGd3B2l>ed%2)4S-ujZ&PLMZmWmi*ub$r z9#z!je@{8sA@X9L+1nSLq@h9Hwj0WeNVZfy=i=-<1bBb^_fS@e*Yi-grP^(9^m&|L z!O|vBxnIP?#{V|#oE3-ZcT4cN8r{J?Jb^WB4%Bb*Y2eFCcx?UM{0`$^8oKK8V0g4co3QN;Y`B^2mBbvS+#s|5bZkl^Mzi{W_XNnr!B;bbQO>Qp8mf%oWBeg9Ut zt>65|P=<&ARcqJ17pR7^7JeFf zZX;*jbn_uDtJhr-yjTVTc_2do!w=nq2(%Dn7~Cx6DG_0VFa>}7=!ANuJt9@?0aRUA z4OZQIQ>2wuRfpiZMix&np>Q6V{`l{f;F^~M%HQCw!O!v+q+(Nl?^SszC$C<&rmOpt zLG0k($fALl1|U(1D=#Z^w7q(gB(5I{AFtQd>t``m?H>e%jTeB=bCSGm&a!{A*Beup^pJWAtPAVnh{i z5qeI20MNr@>wedE2C5JGJEQ~KFdTf_AWTCVzT&Lu#^y=7|Lq_wI;LyqF~@lkaZivSCAA%BHvuNw5#;Iac4J_7$aC=XUadbUmo zEV}Yp2=m*3?r{82``I`ge%YVG z2|wgOr>Ca_+Q@0k?fC9q|576(if^Cuyy+L^Sado(*Uw|N?(iHJ|Mo10vo<}hJ@u=>KyaT$E}ApQ%;(Ro+;{ZO_u)ADS` zCD3xE84BUN0SqunW$w80&%ZO&z$SrITW*zqzsc!;P{TP~1f{*Np|Np_DCb`bzm90Q z`1!=eHJzN)W|qBv=aK*b!uQuhO`Ppecu>$8#S0M4oZ}nh=s$McnmsfzsB+NlUc!C4 z|L`uaGMS~dz3Nmh3AAm4a|U8hK)wndUlqgKIj3AG1RO>u zGjMxNHZ5xA$?-617@?&*N2g_}9vXAsFbm{O@XE@XfbY<n#0B_YX$i$OUXA8WD}D3@!;J&hhrc0R?3-VI^PQ)YRm*Hj13*F+BPM{{jfL zk%!6g@rB{xuW)D+lM{`NMR?f2KsmD0GCS9b_hFxjAZmHk8w~G%PE34sG(JJlJK>SE zG#ssE5fB(?(_ISf?>{@ah}J8!0#q3XWwAFCxh}H2H`OTs-PiD~#C?gyzD&<E~Y1#ZSG_V44eNYOXNDKWG{Lgo8f`{5gSdqg^<*quAR%eVX;< z%XUX^uZ9?9@7i}>$XCEsL4EWCp&sYi!sK;ca_P&sOH(~4G#~c&AFojP-&Au@yL)18oK zGiXUDGYh&>Y1c2kGj99j&$zVL*7%t<+xDrJrSU=*6GaH*xNdRRZ}6-DDMyp~89Y2I zqq(V5%p#6zw!`)4We-=^D1Kbm&#v8OgZ``+_b6L~hHNr#G*fb(qL!~!EcZ)#Ox48|O`z>u)bY6w!rktcud?)69p4EjKGyae=COjXsQq|Sb38Z*;#;75NGorUw zWb~mU<>_RGEqsr(*HG4Tm;q@tYPS^|rt3qg#oRE~^Svca_{|4_JO!B0*M_Mn<~#tM z7Fi_xx_0%da|bH!t7Y7mc3EEW`>MEn9UKN~JCuWD;aHu(0%lG;KRdyZnGvQ!thCov zCDjrt`CqI`xfYuamVe||*EWw55)xWno^$ytH;eGK{?)g)_%U=)WOQ=8O+G{7c7gZ7 zR{xa3d8k*13LdXl0ym&5;gqkoA!w!n5@qExn5ZIdN797xIYkIX5ojn zQ90=R^2*?f{_+LslZl}bs^R=As_h@v5*10>cF|^R=VyWQx&$7*!#08q*t~8%7G#E6 zMcrF*g+Ly4Qh*BdtK6eUp)myjtVnUoq8R^CdvB)2s}Hi*4AMy*HO@-0~&*x-A%uO>xC77_$eQRVJfWR;}(&xZlD zXZVEfYRRg`XZRm55VY_{QtkxIw-DyGKk!qKm&Z`%*k8AAE3q(M5wTceP`GC8XVx(C z(&Nn=UT$usahawtuBFAr>|cF!MaJ*ditW7GISBD_c}##iXplr@bzG)9dp4@qDLWIj zGz^dJtRVQ zOTWU__D2QYruDN;f{a-L=BWZ9-6nL+kq)BS=)vlwxJG|59MH(jZM0nOv705Tkaxma z7Ur?-_aaiFvq4If5j_jEgzZ}Lc^Jper;n+;wVrSQ;FBs&4eAX5F?p_$6eFrF+1;d` z;oPq%KNhP*TmNSGp9xnKOvAL4MV7S6wh-2 z{BrYkQH2M6BEH}@ZAaEkec$Zedlo7_YnzjqE7QPZaT=23YbYolh5mgWatPLR$n0e~ zT)K56U0MOCUjWd+11gx~s_(ytpKbs_y+6ADt@3#^W2?+c9BPVf78(54LkoKpsGjzvKE#IiE(Mva+bi$XU7^?hdA*%?-Q1 zdpl!FT}7(*_xJg}_L`&^8?QhC0{^dq0noM`T^-u$mb7(;hO_g0T*4%ow=^f2ezD)y z&^XBliGm(Kx)0s~Qp_SJJL-V9;kWroUwfULlYUQT=a+TeWNtv-^+bWfcw#_!AS%K$ zF5L50oHt^-aI_8buGAlZ?_WKl5@O`x6X(CKn3aKA#exRR`0qA%{ zlnK&7dwc@(@6o}^N)78>GP%qTAyGd6jGHdkq^GEfPi-q+E(|dc`d#Fn;_qzm7K%!*+H54Oz zB3XmXPC~L7|Jhr6yORcYa9EgbzE0s5uu4eH_5H=cVKY|kL@2fRWDrvYnpWg^{od!z z*||9cXsf}!k;)}kFUNt4bN~3r?ZabZ`D)M!i^rKny=Yi2uN3iS10FnXkBA8 zAe0e9c`p*Ay|$lk#`j6R{&5!ddrS{+=n_)c=rvWDaLc)Ack29sC(x*`9D?rErC~f@ zSAg_pI%Ip#gu1V5z`qAPH@*ul!+>;?_S`D&)wqz)6*vU!DeY}-^GW#GA~Tw=qpdxL zMXLwBK5vQzVZLxJp?R6yJ9#npWYrUM^r88qZNRJF1IQBk1*>_sw>YL*Z~TeujznP} z+$y`O?$~B(3j8Idt?E2#qBJFx8i?LP%D+y5zp(j{YL- z9BRMa7ggZ|F1~?b}ubYJa+e zcqg>xI9#%Q_&i93RYy(9`|VVHW|R3mk8rHzNp*Qm{@ljB-v(@wNpCwCsC(B@MbaZF|uxPbI4V`_?iPP~` zFCMQ8_*MNnx59k7qI0gCGZX;&xpnK6yjEBpZ>UqMAaJF;`A$3WJx#)@hm|Ez%5B9u z*5a0pjZN*=fC>^ibN&T#_yg{NQSAut&zvoB;G6Tl!}PdjeFT?G-da~zltrBTj)(|n z){`?xWlh(=%(Po7epar6b;;3&Yu3`rc0+7}>0na0wCuQc*T&j=D=0hze9Gx4We$44 z3t9=njX@}yrHYgqI>RGwpc51bOy?3onxE$qI=9@OBuVrc(xyO>s%rBpa~1N(V7k1# zydBRX$e43bQF31;SoqtgCri0w($XH&&|Wy!i~aVDTWZK*ws*pXlDZ45ngi*JvTsSc zDgjz!b|#O;@j9bUYW;SXX>_>0}{`%BZ)OKe`XjfLkeux2*q;JsMWXV zqd$kTKrrPoP^zZsW7S1u?!$%wJ7L;`WXYv(P10#UR*9cTF(A3XdME@>^Z;=e_Vc7t zk1`|dXe(np{&&;btqLSbON|-YS3LQIyxF@MY_xs2cV5K#M}=FBKTCMNAu~{ z&Y|J?F~=!@4S*T|c_kG9?`NUEDfAyL3iOo`JKvY#Q?hbovGCy0JAaUPR010=n*J7uYZsoxX6OeHp4UkzFN}>6-W)p?#7h6<|5) zx}Zii+{79+8RHFwM}XOhh;7Qq$RKxS1Tc$kCA{8g1Je)KcY0+4G3}c!aWzSF_KcCN zbM?_q|0#g}jzZV6u$+s)qN}y4Ux6a`nQp)Sy(}P$$LSiO1=qshsVNP+!{7d>z9dHl zr5rfF=PM{WICw~LI6}x{>f)(hzG(>s=Q8Ge2LMC!GD~~t;i|eCH=DF)IfWYi+e6sqP@&xIIZ8~Y<>VWUgmI8PQ7$ET)6){L#wpYbL)duX})EKL@DRM;6`3eS$n)n^b!rk*jmM<2V=?7^EoOV#XB> z*6REArQe%*2{vLRab=RHVUe!!E`uCXc5AbL=#g~o_od9~tnU|N&!X)?iAxCrK(t$) zrFGo*D=pLUR0EI$l*^BVK4}J4-0|&3+mlwNicfQ9%7Cs_^y|{gv#fxdE8IQ+%m=}S z^DLma5P*B>=;^7H!(A{i3oH<15Z+R~0~4|(R3%(0FhjmNAG>@pcbQaG?9bsI0C%y2 zLs%|1Iz5w-UO$@~L;F z7o>Xf@p0kZ?3^;1j#lp+aGO66Ed>*O(^Mtvd7sOjf&C=kr7 zJd%_K$esx%t&pF2+(sVcODv^~qDZZVee&wo=g+Epd;1-|vR|PNg@g)nKed&3{!yAp zi75`|`FrzpQ_xVyoe$b#LfL5^^Sjt@<-Hn%4+mi299Gj4r&XK0Jg?qpt#+$gX`v(U-z%`mbAswt`j>Wu?WDAech4H3+C z#7Z|-UEx%;vEdEve85Kf&wxeQF4pX%V_%6&+bD1QC6V8t{)y3~;bvET37usK3#IPu zgfm}OR^sQ_?KQdxs>u}RRySG8~*I)D;N_?DAu`b23M+pus$U7*(k-ug4zg%ou&~1 z`ecsfKUx5EB(s=(Mkt%qKa(~(^5}x}BSYxf^|rk?2YI_X6e~*%<=S?=0V|AY*wxW| zu8w+ifIJI@zXi3F5%B1C)2hG{_Q5b4F}0?P*Ln%7z-zOJX6NPb=kr*wK@r*!6_1W~ zr$tXCh!%i{x^(i-T%tpzTva$viT6T!kewI)93$sQPizk~FJUdY#?&L{jx}8~W|Xtc zwp@2mNpYd-n9q6n?~w-_u^X^ zJP$hsR9sh4&G1YFl;qQ9R4FnvF`+9<710Hy-Gpp8$j@kSe(cNk=*{D*akn}Urjs5X z4nhunA6Jn!a_??hnzJ_i^*QgVafZd3ln$HyKo(YALPMu@Ep;!(zya1=-Masa;=Z+Z z{iv|s_fh^r9`F9+WXL+uilaZ6`@i5fIlJa{R+7cJ>dw(W6B& z-uokoAy&+Otsmg91lxqyNsI#6%r@G`HJf8u9*QckkXN!i{B=r+xP&2YXOZLU^m6|u z+s*7-(q1-%As+oY`((~gc86ROZ}O#svT8(At1Y(iwTc9n&wfotMyAmqW7XPjv(#qU zx#>S5o+(U|YL=EoSetB$TdgEm?qby<1q)?m#S4*&%$ zQ~@#KU@zwa$Z8pG4f(Ub<7<3>GWXwREXInjtFphTM*Yz zs_Pmm6<)iQ5p<>;e@~Y^-_kR|Nyo|U}N%X+u|@LXCtE>M{r;T7Picn0#Sd$_`)T->uLM(>dst4MI2+ zS>Ow7e0x%)JE7g5yZ1rTZc_OQutF{M=Ytqx>S~-JwZACjx2;rcslFab*=O97h}%m z|DIi(<(M#AU2_6$^JIIU$m5!gAzj()EdTy}HVxdjQ({omdZF0V6EsCvud)_^1omX7 zpfTr8q<;CUMv(20VNdcJ=?MqR5_+A^besGnXghUNolVF*uC<;LL!To{5z#d@HFf)$ z@LgSGo88t*e_`?r`*6nIrt9*8tj*)5BMj@!?3VL$ww;xQT>Mf$Crqf`v#5~TMNcEa zye3)`r)=a-xg|-GWyp)X$TwkjT?B{G#UtIv&{gAajJ&;EVZAcmv$L~k31KV5W=wZ2 z_2|sZa%U<-Qs;-E>9*%aJC6O~`aeJ5PO?cz3gF(6?tYbhT%#ACYbW#meDj#&g>~5- z-e!n0J7~X>)(^zd#FYEY;0hEcVDBl;XEPU@y~v<5LgAopT!tPkIJyUNQfa?QwTlf5 z)Z9QPrvz!+oGEY<8RdWe6~4>psfHYLG-*eS`E;uGT5)Gc>- z$t#cWkk?c|R|@(>j(Z%abtlLeNqg>S85=iRs-;>IBaFRP$%y_8jx#rA2Ywc*^)8xG zYYbc(AKG8N!paN#m{9C#8W+wC3=wH#wP>$T0EejBw&{*-eSa3ijSHk_&yhr#iEKBQ z>@RKhMkgg?RFBZ^NgP#V2PcCEe$dO(pq?*4NK0FJmFHz8;>JHBY1!90O>M@8jPwUA z4)6eg$HD=7y0N)w_^qN|_p&J5lV3mpAO*w}l6Sqg9TVao8O&+Oue%$ZZvn4)MZ@i1 zuflnLjBJ5F{{zam>11~3dSmG(tyiy z1(n&g_(s=70ZGU8@CQu=iEq70Uh$4HvGfpb1926grHFw3ZJTw1a;Dn*-bMCDS!+!P z&%i4HqkzTnbYHIku&Rp#J;}4nbDbYHYwW-^l4k*^YL9;w} zx(R~31P>i+0ICPB0H9Jx@*~EBzt7}|xB&2N;}7`-@Me-qPYxEjPJZ|BTu5)S1yCgL zi3B*ilxYLhhyKPxC9;{>>RYcJj>VeXROg0XyC9FPg;4{b4KE!$o`ZpwuW7GRme}vu;!~L*CG~*(_4JTiNC8=LZ+r%LH(KXcIAbUo1Q!TEEdT8=azpR zdDC6E_yVX?a8(-A*ZEhmMpf#Zt7SU;`YD5@EMmw9va+)LCXI%t zPoFN@0pii8z)-CH_;E2a>-k>+K1Uv**Sb=3RixYo!qf2>{b=j%BpJ17nMXBdyo?JI zKAR{uRhfeARRK7-DRz&2_gbg~(Rv8PD&r|YwY72p6av$Vak-o_VAV6WwWd8;Ngh@S{0txMn@LeFZsE48+<++Nr#X zG`Wj^+pH3x$Eza(h$EdBa}6p%IFf$3bz#e=>((yve#bmlZXy!Agrz?_)~#fdRY0U5 z{OUg(>b$`VLSn1*H^;C@8Dxr4zx7Y02vRQ7G^{C--y)&3| z_|jbVBqYf{ZAo*j;XvJE;xwcq&19Q1$t>=Svu8W2AyFX9_mtj!ZU%3Pk@DI670i0zZE~>%ERy&;mh4-z02T zS?ZHK$;#!QnJW;Zsn4G`>8g!4qCQb>0zn?oJ$MdyfmZ_o!L?r2%nit|3;%Emw;w(P zQWB@4(*Uq1_IUB?U)AC|ZwfsMsjILd4%6Z+_;m7gdsFmlJhLanR8eoj!IVgDy8?xKO{&lfb_USnJWA5T64ink^Q}u@^4s zzPc2nfva5YPeWj+fpEI2!QlLDFJ2xS^fr?(&?vG7;Jm5iDc_sHXCTTqJt=@dUwcAv3e5#b_Jw2GM+j5w+VA>CTg%6%Z9FnsZ$(PAJ=YWi!58SkBBJk&Rj#N! zu*n^2ZdX=PDu%yTGr&#Rjm4!=&iJm0^Kx-%0LHhx7lgk3P8d&r;&rgO?9ljP-wE#5 z-6s~=lv*%aHouNL0}0Al0s$e{^$zz@`;ubVk&uUMykOElN)mlopk=|L12XIiz@i8J zY-<19NG*>RqkPc*2xBzncBI0cwOcG)-o0?`xfi_ z=-@NRuD`=>!d^I0tX}7~()W&!HWjx`19`xDyu!`P0li4WK7oZpe;owat!9c_{+-}L zkI4y{qZbPuh&A!fhT7`z?TeTtUt1!~3azTEmTxA8epFqY{-ercHC^5M4}ERt;fC|+ zEnoRtIT}fx0NNxt_QiBmr_E4a3A30@4v?`Rd;3ZFdBFVH z62Nr-?usznT@6Z9A)s6j)DuN#7v7W=63Zvw0dtNbFy}xVpMh6U;Que`-aH=4_wO54 z(n3-RMW|F{YqOMHMIyVgFG;px1~J6grA;WZ4TC7l3^Vp^ETvG{W5zNDWj72ZBQu8k znD6g*y`JZf=en=geZB7cd0tQdHFG-WJdg7@kK?nvmr+G*Rp7F)oq8=`FvH27PT{}a z2QCe}shUEEaQS%>`PWWynDnrrS|3g}LV_osjC>3zIr)tnO@J}k_>*h}n zp%wn3ysiO9#L*(hc~`L7MJZu8zMTPx*06tb9VXBGTSDgl1UCR69yua^&o>24{HWuU z@@I1y4oQF81D=1b{m=V{z%2M@;{HEHJO4jnDuf7wCh)mY#||{GuZl`$c-=Rr483`WqCOZc{26)*pqe+Q z;$FVAu(d^f9~eLY)IaknJ9>R1SrFvp5Az;=DDd3M?3-gaM|Gm2U3g|ZgWV*nyW1kZM(lt^7z?e^6zdo{e z@YSos04Y1-IM4Y$rM^EYBcm9MI*5X$Wo2Kwu!o`J?c$1OdXfddnS_J{?7*?p0`JkA zhcZ_xHjOe@YBm$t0DhyJChENZjOtMnQ&Rd$)h0Z1p!}^gN2M#B_YP@sm^y-tOS9_B9_6H|(MD-y%xXA3L!-_+y{aKr~ z??IH?C|5bGa^kz4l?HlJBMMzZn5v((7(hXXkj ziF4}tA9(*RGY7^35+l^|?Ue(5zJS56tLKE)nyrw>#?jU7H^jobZ_xf?S(7sdHjU<% z=Oi^V5jabLW8f&d0$3=0!)=+BoO68H7ZrRh5f|f~9`uG@?npRkaKO8mXSyYX$kk?E zydcoLuK+jd7PRG3GLrDg4ROB|9`G2s-C}NO{RJISSzCaY4iF25v3*q|qhmpd6m8{> z|LPcMhB36QA>uS!{Hw?Key`yL)6bn9;pHS)$Eftv>Oa3xsHEKP1_Ek@@9g4>o^d-A z8VkY#G$Z^F5y&67&fP$sDfolzIZEpCwR=G$!CXBO_~n1Nc$cCVuP6ClX=|6Gcnb=^ zQ&GO*}t|X;Dpb( zW%5_~#nHUhp|5-^Y(+GaaWEWT(s-0n@6k24aUveQ-Q;`xX+GtDr@mUY&_|-YNeyVF zS+r;1gCXb2c0c;Z`M1HiDpbezENg-OJqw|)5J>*eV<}PL@%H>QZLZs>U{pl2`5SFJ zAORUh!t&L(pf%8*z43PUT9|?FZCVf19@q<7mi-Ui{8t~u%Z7PTX*wZP^v(B zAz9g-r8+FzVBA{s<7CoSbHtwQFtTQQL&uWl7+fg8(>3DneVjV8?FRB^>RbABx^r+( ze(v(`&?b*$%AYoS19I4~tkP)s<+?cawWhcdw|G6}Hk8BDk~zGive(d;?Bm1z*}}D- z+yB6Qa<;b`ZfvNshdZPH_WH?9a>nX*>Rc{Evs&CsiIaTl){!@opNt@5MmyfZXr*m~a%r`Anuw^FFVPmLw zX>D_aJX)#~M)10YU;0*&5idMA5`I0`C2Gx~&NGoW^tZZQ)x)PYcX=y?UjvU!B`S*< z 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 BWugE8 literal 59833 zcmeFZcTiN@6F&&!kR>CCfMk#?3X(HO63IDdB?rlIKqN`dSwJM`oQEVy$w5G1NRnXy zfg$hZy{GT_t>6B;wOh4S7nr)toYQ@}PoM73r|*O+y_Ckre1M6BgoG_CBdLOf1VR8m zv*@V69m(g63&0;_XBBC2q_QFMO(Y~LBw0x@HFtxZ3>0^wUb4^aU^h2=$nxHvK}&YH_nYp+&1x1{Dd1-rPG@nA*;J4@FeJkHho z-bY{S4V>49j*~?D7e)HAK!!9YWgeG^%I+vik-lR@V5Uct@DHK)=d}Av zg0fXKEbAD&e)PTfwD}aFXDobWeFGk#A;W%0DGdVygAQ?r9}l+0KNJ$u$-p!k4In7K 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

H@iD0tRt@+ZaPfDZBPm?zWrT2W>y$|IFMAE)!b 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

device.connectivity.check-connectivity

+

{{ dialogTitle }}

+ (click)="close()">{{ closeButtonLabel | translate }}

7=_HYoP$BYVE!nEx=UAt%=LeWRShAmnms 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 Date: Mon, 17 Jul 2023 11:49:06 +0300 Subject: [PATCH 079/168] UI: Change units models --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 +-- ui-ngx/src/assets/model/units.json | 9 ++------- 2 files changed, 3 insertions(+), 9 deletions(-) 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 c849b3379c..fd63a8f708 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3931,7 +3931,7 @@ "kelvin": "Kelvin", "rankine": "Rankine", "fahrenheit": "Fahrenheit", - "percentage": "Percentage", + "percent": "Percent", "meter-per-second": "Meter per Second", "kilometer-per-hour": "Kilometer per Hour", "foot-per-second": "Foot per Second", @@ -4099,7 +4099,6 @@ "millimole": "Millimole", "kilomole": "Kilomole", "mole-per-cubic-meter": "Mole per Cubic Meter", - "battery": "Battery", "rssi": "RSSI", "ppm": "Parts Per Million", "ppb": "Parts Per Billion", diff --git a/ui-ngx/src/assets/model/units.json b/ui-ngx/src/assets/model/units.json index ecbda65cca..720719b366 100644 --- a/ui-ngx/src/assets/model/units.json +++ b/ui-ngx/src/assets/model/units.json @@ -1106,9 +1106,9 @@ "tags": ["concentration","amount of substance","mole per cubic meter","mol/m³"] }, { - "name": "unit.battery", + "name": "unit.percent", "symbol": "%", - "tags": ["power source","state of charge (SoC)","battery","battery level","level","humidity","moisture", + "tags": ["power source","state of charge (SoC)","battery","battery level","level","humidity","moisture","percentage", "relative humidity","water content","soil moisture","irrigation","water in soil","soil water content","VWC", "Volumetric Water Content","Total Harmonic Distortion","THD","power quality","UV Transmittance","%"] }, @@ -1453,11 +1453,6 @@ "symbol": "richter", "tags": ["earthquake","seismic activity","richter"] }, - { - "name": "unit.percentage", - "symbol": "%", - "tags": ["percentage"] - }, { "name": "unit.second", "symbol": "s", From df9ec02bbc2d97ec8616b623956ebdcaf57fd70c Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 17 Jul 2023 12:30:52 +0300 Subject: [PATCH 080/168] UI: Optimize rxjs observable in unit selector --- .../shared/components/unit-input.component.ts | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/ui-ngx/src/app/shared/components/unit-input.component.ts b/ui-ngx/src/app/shared/components/unit-input.component.ts index 8700151c69..01a5db32d0 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.ts +++ b/ui-ngx/src/app/shared/components/unit-input.component.ts @@ -16,9 +16,9 @@ import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { EMPTY, Observable, of, ReplaySubject, switchMap } from 'rxjs'; +import { Observable, of, shareReplay, switchMap } from 'rxjs'; import { searchUnits, Unit, unitBySymbol } from '@shared/models/unit.models'; -import { map, mergeMap, share, startWith, tap } from 'rxjs/operators'; +import { map, mergeMap, tap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { ResourcesService } from '@core/services/resources.service'; @@ -75,17 +75,16 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit { this.updateView(value); }), map(value => (value as Unit)?.symbol ? (value as Unit).symbol : (value ? value as string : '')), - mergeMap(symbol => this.fetchUnits(symbol) ) + mergeMap(symbol => this.fetchUnits(symbol)) ); } writeValue(symbol?: string): void { this.searchText = ''; this.modelValue = symbol; - EMPTY.pipe( - startWith(''), - switchMap(() => symbol - ? this.unitsConstant().pipe(map(units => unitBySymbol(units, symbol) ?? symbol)) + of(symbol).pipe( + switchMap(value => value + ? this.unitsConstant().pipe(map(units => unitBySymbol(units, value) ?? value)) : of(null)) ).subscribe(result => { this.unitsFormControl.patchValue(result, {emitEvent: false}); @@ -158,12 +157,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit { name: this.translate.instant(u.name), tags: u.tags }))), - share({ - connector: () => new ReplaySubject(1), - resetOnError: false, - resetOnComplete: false, - resetOnRefCountZero: false - }) + shareReplay(1) ); } return this.fetchUnits$; From 93c27eab8f6d46c3b75c327cd12a0c79391f4566 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 17 Jul 2023 15:58:01 +0300 Subject: [PATCH 081/168] UI: Change check connectivity dialog title and fix state --- ...e-check-connectivity-dialog.component.html | 6 +++--- ...ice-check-connectivity-dialog.component.ts | 19 ++++++++++++++++--- .../device/devices-table-config.resolver.ts | 4 ++-- .../assets/locale/locale.constant-en_US.json | 1 + 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html index 75e8887da6..a0991570eb 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html @@ -16,7 +16,7 @@ --> -

diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts index 9e1639740e..8a427512aa 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts @@ -42,10 +42,11 @@ import { } from '@shared/models/device.models'; import { UserSettingsService } from '@core/http/user-settings.service'; import { ActionPreferencesUpdateUserSettings } from '@core/auth/auth.actions'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; export interface DeviceCheckConnectivityDialogData { deviceId: EntityId; - showDontShowAgain: boolean; + afterAdd: boolean; } @Component({ selector: 'tb-device-check-connectivity-dialog', @@ -70,7 +71,9 @@ export class DeviceCheckConnectivityDialogComponent extends DeviceTransportType = DeviceTransportType; deviceTransportTypeTranslationMap = deviceTransportTypeTranslationMap; - showDontShowAgain = this.data.showDontShowAgain; + showDontShowAgain: boolean; + dialogTitle: string; + closeButtonLabel: string; notShowAgain = false; @@ -90,6 +93,16 @@ export class DeviceCheckConnectivityDialogComponent extends private userSettingsService: UserSettingsService, private zone: NgZone) { super(store, router, dialogRef); + + if (this.data.afterAdd) { + this.dialogTitle = 'device.connectivity.device-created-check-connectivity'; + this.closeButtonLabel = 'action.skip'; + this.showDontShowAgain = true; + } else { + this.dialogTitle = 'device.connectivity.check-connectivity'; + this.closeButtonLabel = 'action.close'; + this.showDontShowAgain = false; + } } ngOnInit() { @@ -156,7 +169,7 @@ export class DeviceCheckConnectivityDialogComponent extends (data) => { this.latestTelemetry = data.reduce>((accumulator, item) => { if (item.key === 'active') { - this.status = item.value; + this.status = coerceBooleanProperty(item.value); } else if (item.lastUpdateTs > this.currentTime) { accumulator.push(item); } diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 1c81d3da9b..6682e87551 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -720,7 +720,7 @@ export class DevicesTableConfigResolver implements Resolve Date: Mon, 17 Jul 2023 16:28:17 +0300 Subject: [PATCH 082/168] UI: Refactoring tables --- .../home/components/entity/entities-table.component.html | 4 ++-- .../components/widget/lib/alarms-table-widget.component.html | 4 ++-- .../components/widget/lib/alarms-table-widget.component.ts | 4 ++-- .../widget/lib/entities-table-widget.component.html | 4 ++-- .../components/widget/lib/entities-table-widget.component.ts | 4 ++-- .../alarm/alarms-table-widget-settings.component.html | 4 ++-- .../settings/alarm/alarms-table-widget-settings.component.ts | 4 ++-- .../cards/entities-table-widget-settings.component.html | 4 ++-- .../cards/entities-table-widget-settings.component.ts | 4 ++-- .../cards/timeseries-table-widget-settings.component.html | 4 ++-- .../cards/timeseries-table-widget-settings.component.ts | 4 ++-- .../modules/home/components/widget/lib/table-widget.models.ts | 2 +- .../widget/lib/timeseries-table-widget.component.html | 4 ++-- .../widget/lib/timeseries-table-widget.component.ts | 4 ++-- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 15 files changed, 28 insertions(+), 28 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html index 65f4bed2ae..3b539cdba5 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html @@ -218,7 +218,7 @@ -
+
-
+
-
+
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.ts index 6bd7d62c12..223cf93e77 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.ts @@ -47,7 +47,7 @@ export class AlarmsTableWidgetSettingsComponent extends WidgetSettingsComponent enableFilter: true, enableStickyHeader: true, enableStickyAction: true, - collapseCellActions: true, + showCellActionsMenu: true, reserveSpaceForHiddenAction: 'true', displayDetails: true, allowAcknowledgment: true, @@ -70,7 +70,7 @@ export class AlarmsTableWidgetSettingsComponent extends WidgetSettingsComponent enableFilter: [settings.enableFilter, []], enableStickyHeader: [settings.enableStickyHeader, []], enableStickyAction: [settings.enableStickyAction, []], - collapseCellActions: [settings.collapseCellActions, []], + showCellActionsMenu: [settings.showCellActionsMenu, []], reserveSpaceForHiddenAction: [settings.reserveSpaceForHiddenAction, []], displayDetails: [settings.displayDetails, []], allowAcknowledgment: [settings.allowAcknowledgment, []], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html index 818b18e6ad..f8867b9f6a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html @@ -66,8 +66,8 @@ {{ 'widgets.table.enable-sticky-action' | translate }} - - {{ 'widgets.table.collapse-cell-actions-mobile' | translate }} + + {{ 'widgets.table.show-cell-actions-menu-mobile' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.ts index f73a86069e..402d6bba4f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.ts @@ -45,7 +45,7 @@ export class EntitiesTableWidgetSettingsComponent extends WidgetSettingsComponen enableSelectColumnDisplay: true, enableStickyHeader: true, enableStickyAction: true, - collapseCellActions: true, + showCellActionsMenu: true, reserveSpaceForHiddenAction: 'true', displayEntityName: true, entityNameColumnTitle: '', @@ -67,7 +67,7 @@ export class EntitiesTableWidgetSettingsComponent extends WidgetSettingsComponen enableSelectColumnDisplay: [settings.enableSelectColumnDisplay, []], enableStickyHeader: [settings.enableStickyHeader, []], enableStickyAction: [settings.enableStickyAction, []], - collapseCellActions: [settings.collapseCellActions, []], + showCellActionsMenu: [settings.showCellActionsMenu, []], reserveSpaceForHiddenAction: [settings.reserveSpaceForHiddenAction, []], displayEntityName: [settings.displayEntityName, []], entityNameColumnTitle: [settings.entityNameColumnTitle, []], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html index bb286c83a7..170d460f92 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html @@ -39,8 +39,8 @@ {{ 'widgets.table.enable-sticky-action' | translate }} - - {{ 'widgets.table.collapse-cell-actions-mobile' | translate }} + + {{ 'widgets.table.show-cell-actions-menu-mobile' | translate }} widgets.table.hidden-cell-button-display-mode diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts index edec3990e5..eaa86f6503 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts @@ -44,7 +44,7 @@ export class TimeseriesTableWidgetSettingsComponent extends WidgetSettingsCompon enableSelectColumnDisplay: true, enableStickyHeader: true, enableStickyAction: true, - collapseCellActions: true, + showCellActionsMenu: true, reserveSpaceForHiddenAction: 'true', showTimestamp: true, showMilliseconds: false, @@ -64,7 +64,7 @@ export class TimeseriesTableWidgetSettingsComponent extends WidgetSettingsCompon enableSelectColumnDisplay: [settings.enableSelectColumnDisplay, []], enableStickyHeader: [settings.enableStickyHeader, []], enableStickyAction: [settings.enableStickyAction, []], - collapseCellActions: [settings.collapseCellActions, []], + showCellActionsMenu: [settings.showCellActionsMenu, []], reserveSpaceForHiddenAction: [settings.reserveSpaceForHiddenAction, []], showTimestamp: [settings.showTimestamp, []], showMilliseconds: [settings.showMilliseconds, []], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts index 9de4601e02..be447d1dde 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -33,7 +33,7 @@ export interface TableWidgetSettings { enableSearch: boolean; enableSelectColumnDisplay: boolean; enableStickyAction: boolean; - collapseCellActions: boolean; + showCellActionsMenu: boolean; enableStickyHeader: boolean; displayPagination: boolean; defaultPageSize: number; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html index 633b1c7455..655c23bfcf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html @@ -67,7 +67,7 @@ -
+
-
+
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:re.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:oe.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ae.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Ze,decorators:[{type:n,args:[{selector:"tb-action-node-clear-alarm-config",template:'
\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 \n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder},{type:X.NodeScriptTestService},{type:j.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class et extends s{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.alarmSeverities=Object.keys(c),this.alarmSeverityTranslationMap=f,this.separatorKeysCodes=[ie,le,se],this.tbelEnabled=W(this.store).tbelEnabled,this.scriptLanguage=d}configForm(){return this.createAlarmConfigForm}onConfigurationSet(e){this.createAlarmConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:d.JS,[D.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([D.required]),this.createAlarmConfigForm.get("severity").setValidators([D.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e});let r=this.createAlarmConfigForm.get("scriptLang").value;r!==d.TBEL||this.tbelEnabled||(r=d.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===d.JS?[D.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(o&&r===d.TBEL?[D.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=d.JS)),e}testScript(){const e=this.createAlarmConfigForm.get("scriptLang").value,t=e===d.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",n=e===d.JS?"rulenode/create_alarm_node_script_fn":"rulenode/tbel/create_alarm_node_script_fn",r=this.createAlarmConfigForm.get(t).value;this.nodeScriptTestService.testNodeScript(r,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,n,e).subscribe((e=>{e&&this.createAlarmConfigForm.get(t).setValue(e)}))}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===d.JS&&this.jsFuncComponent.validateOnSubmit()}}}e("CreateAlarmConfigComponent",et),et.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:et,deps:[{token:G.Store},{token:E.UntypedFormBuilder},{token:X.NodeScriptTestService},{token:j.TranslateService}],target:t.ɵɵFactoryTarget.Component}),et.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:et,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 tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \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:re.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:oe.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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:ue.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:ue.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:ue.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:ue.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:_.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:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ae.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:et,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 tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \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:G.Store},{type:E.UntypedFormBuilder},{type:X.NodeScriptTestService},{type:j.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class tt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(g),this.directionTypeTranslations=y,this.entityType=x}configForm(){return this.createRelationConfigForm}onConfigurationSet(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[D.required]],entityType:[e?e.entityType:null,[D.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[D.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[D.required,D.min(0)]]})}validatorTriggers(){return["entityType"]}updateValidators(e){const t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([D.required,D.pattern(/.*\S.*/)]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==x.DEVICE&&t!==x.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([D.required,D.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",tt),tt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:tt,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),tt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:tt,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 \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \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:pe.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:tt,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 \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \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:G.Store},{type:E.UntypedFormBuilder}]}});class nt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(g),this.directionTypeTranslations=y,this.entityType=x}configForm(){return this.deleteRelationConfigForm}onConfigurationSet(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[D.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[D.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[D.required,D.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([D.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&n?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([D.required,D.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",nt),nt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:nt,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),nt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:nt,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 \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \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:pe.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:nt,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 \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \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:G.Store},{type:E.UntypedFormBuilder}]}});class rt extends s{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,D.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,D.required]})}}e("DeviceProfileConfigComponent",rt),rt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:rt,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),rt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:rt,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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:_.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:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:rt,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:G.Store},{type:E.UntypedFormBuilder}]}});class ot extends s{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.tbelEnabled=W(this.store).tbelEnabled,this.scriptLanguage=d,this.serviceType=p.TB_RULE_ENGINE}configForm(){return this.generatorConfigForm}onConfigurationSet(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[D.required,D.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[D.required,D.min(1)]],originator:[e?e.originator:null,[]],scriptLang:[e?e.scriptLang:d.JS,[D.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]],queueName:[e?e.queueName:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.generatorConfigForm.get("scriptLang").value;t!==d.TBEL||this.tbelEnabled||(t=d.JS,this.generatorConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.generatorConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.generatorConfigForm.get("jsScript").setValidators(t===d.JS?[D.required]:[]),this.generatorConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.generatorConfigForm.get("tbelScript").setValidators(t===d.TBEL?[D.required]:[]),this.generatorConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=d.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(){const e=this.generatorConfigForm.get("scriptLang").value,t=e===d.JS?"jsScript":"tbelScript",n=e===d.JS?"rulenode/generator_node_script_fn":"rulenode/tbel/generator_node_script_fn",r=this.generatorConfigForm.get(t).value;this.nodeScriptTestService.testNodeScript(r,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId,n,e).subscribe((e=>{e&&this.generatorConfigForm.get(t).setValue(e)}))}onValidate(){this.generatorConfigForm.get("scriptLang").value===d.JS&&this.jsFuncComponent.validateOnSubmit()}}var at;e("GeneratorConfigComponent",ot),ot.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ot,deps:[{token:G.Store},{token:E.UntypedFormBuilder},{token:X.NodeScriptTestService},{token:j.TranslateService}],target:t.ɵɵFactoryTarget.Component}),ot.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:ot,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
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.EntitySelectComponent,selector:"tb-entity-select",inputs:["allowedEntityTypes","useAliasEntityTypes","required","disabled"]},{kind:"component",type:Y.QueueAutocompleteComponent,selector:"tb-queue-autocomplete",inputs:["labelText","requiredText","autocompleteHint","subscriptSizing","required","queueType","disabled"]},{kind:"component",type:re.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:oe.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ae.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ot,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
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder},{type:X.NodeScriptTestService},{type:j.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"}(at||(at={}));const it=new Map([[at.CUSTOMER,"tb.rulenode.originator-customer"],[at.TENANT,"tb.rulenode.originator-tenant"],[at.RELATED,"tb.rulenode.originator-related"],[at.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"],[at.ENTITY,"tb.rulenode.originator-entity"]]);var lt;!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(lt||(lt={}));const st=new Map([[lt.CIRCLE,"tb.rulenode.perimeter-circle"],[lt.POLYGON,"tb.rulenode.perimeter-polygon"]]);var mt;!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(mt||(mt={}));const ut=new Map([[mt.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[mt.SECONDS,"tb.rulenode.time-unit-seconds"],[mt.MINUTES,"tb.rulenode.time-unit-minutes"],[mt.HOURS,"tb.rulenode.time-unit-hours"],[mt.DAYS,"tb.rulenode.time-unit-days"]]);var pt;!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(pt||(pt={}));const dt=new Map([[pt.METER,"tb.rulenode.range-unit-meter"],[pt.KILOMETER,"tb.rulenode.range-unit-kilometer"],[pt.FOOT,"tb.rulenode.range-unit-foot"],[pt.MILE,"tb.rulenode.range-unit-mile"],[pt.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);var ct;!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"}(ct||(ct={}));const ft=new Map([[ct.ID,"tb.rulenode.entity-details-id"],[ct.TITLE,"tb.rulenode.entity-details-title"],[ct.COUNTRY,"tb.rulenode.entity-details-country"],[ct.STATE,"tb.rulenode.entity-details-state"],[ct.CITY,"tb.rulenode.entity-details-city"],[ct.ZIP,"tb.rulenode.entity-details-zip"],[ct.ADDRESS,"tb.rulenode.entity-details-address"],[ct.ADDRESS2,"tb.rulenode.entity-details-address2"],[ct.PHONE,"tb.rulenode.entity-details-phone"],[ct.EMAIL,"tb.rulenode.entity-details-email"],[ct.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);var gt;!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(gt||(gt={}));const yt=new Map([[gt.FIRST,"tb.rulenode.first"],[gt.LAST,"tb.rulenode.last"],[gt.ALL,"tb.rulenode.all"]]),xt=new Map([[gt.FIRST,"tb.rulenode.first-mode-hint"],[gt.LAST,"tb.rulenode.last-mode-hint"],[gt.ALL,"tb.rulenode.all-mode-hint"]]);var bt,ht;!function(e){e.ASC="ASC",e.DESC="DESC"}(bt||(bt={})),function(e){e.ATTRIBUTES="ATTRIBUTES",e.LATEST_TELEMETRY="LATEST_TELEMETRY",e.FIELDS="FIELDS"}(ht||(ht={}));const Ct=new Map([[ht.ATTRIBUTES,"tb.rulenode.attributes"],[ht.LATEST_TELEMETRY,"tb.rulenode.latest-telemetry"],[ht.FIELDS,"tb.rulenode.fields"]]),vt=new Map([[ht.ATTRIBUTES,"tb.rulenode.add-mapped-attribute-to"],[ht.LATEST_TELEMETRY,"tb.rulenode.add-mapped-latest-telemetry-to"],[ht.FIELDS,"tb.rulenode.add-mapped-fields-to"]]),Ft=new Map([[bt.ASC,"tb.rulenode.ascending"],[bt.DESC,"tb.rulenode.descending"]]);var Lt;!function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(Lt||(Lt={}));const kt=new Map([[Lt.STANDARD,"tb.rulenode.sqs-queue-standard"],[Lt.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Tt=["anonymous","basic","cert.PEM"],It=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),Nt=["sas","cert.PEM"],St=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);var qt;!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(qt||(qt={}));const Mt=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],At=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 Gt;!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"}(Gt||(Gt={}));const Et=new Map([[Gt.CUSTOM,{value:Gt.CUSTOM,name:"Custom Function",description:"Use this function to specify complex mathematical expression.",minArgs:1,maxArgs:16}],[Gt.ADD,{value:Gt.ADD,name:"Addition",description:"x + y",minArgs:2,maxArgs:2}],[Gt.SUB,{value:Gt.SUB,name:"Subtraction",description:"x - y",minArgs:2,maxArgs:2}],[Gt.MULT,{value:Gt.MULT,name:"Multiplication",description:"x * y",minArgs:2,maxArgs:2}],[Gt.DIV,{value:Gt.DIV,name:"Division",description:"x / y",minArgs:2,maxArgs:2}],[Gt.SIN,{value:Gt.SIN,name:"Sine",description:"Returns the trigonometric sine of an angle in radians.",minArgs:1,maxArgs:1}],[Gt.SINH,{value:Gt.SINH,name:"Hyperbolic sine",description:"Returns the hyperbolic sine of an argument.",minArgs:1,maxArgs:1}],[Gt.COS,{value:Gt.COS,name:"Cosine",description:"Returns the trigonometric cosine of an angle in radians.",minArgs:1,maxArgs:1}],[Gt.COSH,{value:Gt.COSH,name:"Hyperbolic cosine",description:"Returns the hyperbolic cosine of an argument.",minArgs:1,maxArgs:1}],[Gt.TAN,{value:Gt.TAN,name:"Tangent",description:"Returns the trigonometric tangent of an angle in radians",minArgs:1,maxArgs:1}],[Gt.TANH,{value:Gt.TANH,name:"Hyperbolic tangent",description:"Returns the hyperbolic tangent of an argument",minArgs:1,maxArgs:1}],[Gt.ACOS,{value:Gt.ACOS,name:"Arc cosine",description:"Returns the arc cosine of an argument",minArgs:1,maxArgs:1}],[Gt.ASIN,{value:Gt.ASIN,name:"Arc sine",description:"Returns the arc sine of an argument",minArgs:1,maxArgs:1}],[Gt.ATAN,{value:Gt.ATAN,name:"Arc tangent",description:"Returns the arc tangent of an argument",minArgs:1,maxArgs:1}],[Gt.ATAN2,{value:Gt.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}],[Gt.EXP,{value:Gt.EXP,name:"Exponential",description:"Returns Euler's number e raised to the power of an argument",minArgs:1,maxArgs:1}],[Gt.EXPM1,{value:Gt.EXPM1,name:"Exponential minus one",description:"Returns Euler's number e raised to the power of an argument minus one",minArgs:1,maxArgs:1}],[Gt.SQRT,{value:Gt.SQRT,name:"Square",description:"Returns the correctly rounded positive square root of an argument",minArgs:1,maxArgs:1}],[Gt.CBRT,{value:Gt.CBRT,name:"Cube root",description:"Returns the cube root of an argument",minArgs:1,maxArgs:1}],[Gt.GET_EXP,{value:Gt.GET_EXP,name:"Get exponent",description:"Returns the unbiased exponent used in the representation of an argument",minArgs:1,maxArgs:1}],[Gt.HYPOT,{value:Gt.HYPOT,name:"Square root",description:"Returns the square root of the squares of the arguments",minArgs:2,maxArgs:2}],[Gt.LOG,{value:Gt.LOG,name:"Logarithm",description:"Returns the natural logarithm of an argument",minArgs:1,maxArgs:1}],[Gt.LOG10,{value:Gt.LOG10,name:"Base 10 logarithm",description:"Returns the base 10 logarithm of an argument",minArgs:1,maxArgs:1}],[Gt.LOG1P,{value:Gt.LOG1P,name:"Logarithm of the sum",description:"Returns the natural logarithm of the sum of an argument",minArgs:1,maxArgs:1}],[Gt.CEIL,{value:Gt.CEIL,name:"Ceiling",description:"Returns the smallest (closest to negative infinity) of an argument",minArgs:1,maxArgs:1}],[Gt.FLOOR,{value:Gt.FLOOR,name:"Floor",description:"Returns the largest (closest to positive infinity) of an argument",minArgs:1,maxArgs:1}],[Gt.FLOOR_DIV,{value:Gt.FLOOR_DIV,name:"Floor division",description:"Returns the largest (closest to positive infinity) of the arguments",minArgs:2,maxArgs:2}],[Gt.FLOOR_MOD,{value:Gt.FLOOR_MOD,name:"Floor modulus",description:"Returns the floor modulus of the arguments",minArgs:2,maxArgs:2}],[Gt.ABS,{value:Gt.ABS,name:"Absolute",description:"Returns the absolute value of an argument",minArgs:1,maxArgs:1}],[Gt.MIN,{value:Gt.MIN,name:"Min",description:"Returns the smaller of the arguments",minArgs:2,maxArgs:2}],[Gt.MAX,{value:Gt.MAX,name:"Max",description:"Returns the greater of the arguments",minArgs:2,maxArgs:2}],[Gt.POW,{value:Gt.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}],[Gt.SIGNUM,{value:Gt.SIGNUM,name:"Sign of a real number",description:"Returns the signum function of the argument",minArgs:1,maxArgs:1}],[Gt.RAD,{value:Gt.RAD,name:"Radian",description:"Converts an angle measured in degrees to an approximately equivalent angle measured in radians",minArgs:1,maxArgs:1}],[Gt.DEG,{value:Gt.DEG,name:"Degrees",description:"Converts an angle measured in radians to an approximately equivalent angle measured in degrees.",minArgs:1,maxArgs:1}]]);var Dt,Vt,wt;!function(e){e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES",e.CONSTANT="CONSTANT",e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA"}(Dt||(Dt={})),function(e){e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES",e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA"}(Vt||(Vt={})),function(e){e.DATA="DATA",e.METADATA="METADATA"}(wt||(wt={}));const Pt=new Map([[wt.DATA,"tb.rulenode.message"],[wt.METADATA,"tb.rulenode.metadata"]]),Rt=new Map([[Dt.ATTRIBUTE,"tb.rulenode.attribute-type"],[Dt.TIME_SERIES,"tb.rulenode.time-series-type"],[Dt.CONSTANT,"tb.rulenode.constant-type"],[Dt.MESSAGE_BODY,"tb.rulenode.message-body-type"],[Dt.MESSAGE_METADATA,"tb.rulenode.message-metadata-type"]]),Ot=["x","y","z","a","b","c","d","k","l","m","n","o","p","r","s","t"];var Ht,Kt;!function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE",e.CLIENT_SCOPE="CLIENT_SCOPE"}(Ht||(Ht={})),function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE"}(Kt||(Kt={}));const Bt=new Map([[Ht.SHARED_SCOPE,"tb.rulenode.shared-scope"],[Ht.SERVER_SCOPE,"tb.rulenode.server-scope"],[Ht.CLIENT_SCOPE,"tb.rulenode.client-scope"]]);class Ut extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=lt,this.perimeterTypes=Object.keys(lt),this.perimeterTypeTranslationMap=st,this.rangeUnits=Object.keys(pt),this.rangeUnitTranslationMap=dt,this.timeUnits=Object.keys(mt),this.timeUnitsTranslationMap=ut}configForm(){return this.geoActionConfigForm}onConfigurationSet(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[D.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[D.required]],perimeterType:[e?e.perimeterType:null,[D.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,[D.required,D.min(1),D.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[D.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[D.required,D.min(1),D.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[D.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([D.required]):this.geoActionConfigForm.get("perimeterKeyName").setValidators([]),t||n!==lt.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([D.required,D.min(-90),D.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([D.required,D.min(-180),D.max(180)]),this.geoActionConfigForm.get("range").setValidators([D.required,D.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([D.required])),t||n!==lt.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([D.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",Ut),Ut.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Ut,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ut.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Ut,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-message-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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Ut,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-message-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:G.Store},{type:E.UntypedFormBuilder}]}});class zt extends s{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.tbelEnabled=W(this.store).tbelEnabled,this.scriptLanguage=d}configForm(){return this.logConfigForm}onConfigurationSet(e){this.logConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:d.JS,[D.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.logConfigForm.get("scriptLang").value;t!==d.TBEL||this.tbelEnabled||(t=d.JS,this.logConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.logConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.logConfigForm.get("jsScript").setValidators(t===d.JS?[D.required]:[]),this.logConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.logConfigForm.get("tbelScript").setValidators(t===d.TBEL?[D.required]:[]),this.logConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=d.JS)),e}testScript(){const e=this.logConfigForm.get("scriptLang").value,t=e===d.JS?"jsScript":"tbelScript",n=e===d.JS?"rulenode/log_node_script_fn":"rulenode/tbel/log_node_script_fn",r=this.logConfigForm.get(t).value;this.nodeScriptTestService.testNodeScript(r,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId,n,e).subscribe((e=>{e&&this.logConfigForm.get(t).setValue(e)}))}onValidate(){this.logConfigForm.get("scriptLang").value===d.JS&&this.jsFuncComponent.validateOnSubmit()}}e("LogConfigComponent",zt),zt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:zt,deps:[{token:G.Store},{token:E.UntypedFormBuilder},{token:X.NodeScriptTestService},{token:j.TranslateService}],target:t.ɵɵFactoryTarget.Component}),zt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:zt,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',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:re.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:oe.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ae.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:zt,decorators:[{type:n,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n \n \n
\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder},{type:X.NodeScriptTestService},{type:j.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class _t extends s{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,[D.required,D.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[D.required]]})}}e("MsgCountConfigComponent",_t),_t.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:_t,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),_t.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:_t,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:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:_t,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:G.Store},{type:E.UntypedFormBuilder}]}});class jt extends s{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,[D.required,D.min(1),D.max(1e5)]]})}validatorTriggers(){return["useMetadataPeriodInSecondsPatterns"]}updateValidators(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([D.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([D.required,D.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})}}e("MsgDelayConfigComponent",jt),jt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:jt,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),jt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:jt,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 \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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:jt,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 \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:G.Store},{type:E.UntypedFormBuilder}]}});class $t extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.pushToCloudConfigForm}onConfigurationSet(e){this.pushToCloudConfigForm=this.fb.group({scope:[e?e.scope:null,[D.required]]})}}e("PushToCloudConfigComponent",$t),$t.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:$t,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),$t.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:$t,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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:$t,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:G.Store},{type:E.UntypedFormBuilder}]}});class Qt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.pushToEdgeConfigForm}onConfigurationSet(e){this.pushToEdgeConfigForm=this.fb.group({scope:[e?e.scope:null,[D.required]]})}}e("PushToEdgeConfigComponent",Qt),Qt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Qt,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Qt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Qt,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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Qt,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:G.Store},{type:E.UntypedFormBuilder}]}});class Jt extends s{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",Jt),Jt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Jt,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Jt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Jt,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:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Jt,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:G.Store},{type:E.UntypedFormBuilder}]}});class Yt extends s{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,[D.required,D.min(0)]]})}}e("RpcRequestConfigComponent",Yt),Yt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Yt,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Yt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Yt,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:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Yt,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:G.Store},{type:E.UntypedFormBuilder}]}});class Wt extends b{get required(){return this.requiredValue}set required(e){this.requiredValue=ce(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(V),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,[D.required]],value:[e[n],[D.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:["",[D.required]],value:["",[D.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",Wt),Wt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Wt,deps:[{token:G.Store},{token:j.TranslateService},{token:t.Injector},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Wt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Wt,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:w,useExisting:a((()=>Wt)),multi:!0},{provide:P,useExisting:a((()=>Wt)),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 .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"],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:fe.TbErrorComponent,selector:"tb-error",inputs:["error"]},{kind:"component",type:oe.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:oe.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ge.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:_.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:_.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:_.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:_.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:ye.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Wt,decorators:[{type:n,args:[{selector:"tb-kv-map-config-old",providers:[{provide:w,useExisting:a((()=>Wt)),multi:!0},{provide:P,useExisting:a((()=>Wt)),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 .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"]}]}],ctorParameters:function(){return[{type:G.Store},{type:j.TranslateService},{type:t.Injector},{type:E.UntypedFormBuilder}]},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 Xt extends s{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,[D.required,D.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[D.required]]})}prepareOutputConfig(e){return e.tableName=e.tableName.trim(),e}}e("SaveToCustomTableConfigComponent",Xt),Xt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Xt,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Xt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Xt,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:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Wt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Xt,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:G.Store},{type:E.UntypedFormBuilder}]}});class Zt extends s{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,[D.required,D.min(0)]],skipLatestPersistence:[!!e&&e.skipLatestPersistence,[]],useServerTs:[!!e&&e.useServerTs,[]]})}}e("TimeseriesConfigComponent",Zt),Zt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Zt,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Zt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Zt,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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Zt,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:G.Store},{type:E.UntypedFormBuilder}]}});class en extends s{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,[D.required,D.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[D.required,D.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("UnassignCustomerConfigComponent",en),en.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:en,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),en.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:en,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 \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:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:en,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 \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:G.Store},{type:E.UntypedFormBuilder}]}});class tn extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=m,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u,this.separatorKeysCodes=[ie,le,se]}configForm(){return this.deleteAttributesConfigForm}onConfigurationSet(e){this.deleteAttributesConfigForm=this.fb.group({scope:[e?e.scope:null,[D.required]],keys:[e?e.keys:null,[D.required]],sendAttributesDeletedNotification:[!!e&&e.sendAttributesDeletedNotification,[]],notifyDevice:[!!e&&e.notifyDevice,[]]}),this.deleteAttributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==m.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.9",ngImport:t,type:tn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),tn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",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 \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n \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-delete-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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:ue.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:ue.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:ue.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:ue.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:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",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 \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n \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-delete-hint
\n
\n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]},propDecorators:{attributeChipList:[{type:o,args:["attributeChipList"]}]}});class nn extends b{get function(){return this.functionValue}set function(e){e&&this.functionValue!==e&&(this.functionValue=e,this.setupArgumentsFormGroup(!0))}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.maxArgs=16,this.minArgs=1,this.displayArgumentName=!1,this.mathFunctionMap=Et,this.ArgumentType=Dt,this.attributeScopeMap=Bt,this.argumentTypeResultMap=Rt,this.arguments=Object.values(Dt),this.attributeScope=Object.values(Ht),this.propagateChange=null,this.valueChangeSubscription=[]}ngOnInit(){this.ngControl=this.injector.get(V),null!=this.ngControl&&(this.ngControl.valueAccessor=this),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()}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.argumentsFormGroup.get("arguments").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.argumentsFormGroup.get("arguments").removeAt(e),this.updateArgumentNames()}addArgument(e=!0){const t=this.argumentsFormGroup.get("arguments"),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===Gt.CUSTOM),this.argumentsFormGroup){for(this.argumentsFormGroup.get("arguments").setValidators([D.minLength(this.minArgs),D.maxLength(this.maxArgs)]),this.argumentsFormGroup.get("arguments").value.length>this.maxArgs&&(this.argumentsFormGroup.get("arguments").controls.length=this.maxArgs);this.argumentsFormGroup.get("arguments").value.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===Dt.ATTRIBUTE?e.get("attributeScope").enable({emitEvent:!1}):e.get("attributeScope").disable({emitEvent:!1}),t&&t!==Dt.CONSTANT?e.get("defaultValue").enable({emitEvent:!1}):e.get("defaultValue").disable({emitEvent:!1})}updateArgumentNames(){this.argumentsFormGroup.get("arguments").controls.forEach(((e,t)=>{e.get("name").setValue(Ot[t])}))}updateModel(){const e=this.argumentsFormGroup.get("arguments").value;e.length&&this.argumentsFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}}e("ArgumentsMapConfigComponent",nn),nn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:nn,deps:[{token:G.Store},{token:j.TranslateService},{token:t.Injector},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),nn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:nn,selector:"tb-arguments-map-config",inputs:{disabled:"disabled",function:"function"},providers:[{provide:w,useExisting:a((()=>nn)),multi:!0},{provide:P,useExisting:a((()=>nn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n\n
\n \n \n
\n \n
\n {{argumentControl.get(\'name\').value}}.\n
\n
\n \n tb.rulenode.argument-type-field-input\n \n \n {{ argumentTypeResultMap.get(argument) | translate }}\n \n \n \n tb.rulenode.argument-type-field-input-required\n \n \n \n tb.rulenode.argument-key-field-input\n \n help\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
\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 tb.rulenode.default-value-field-input\n \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:oe.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:oe.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:z.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ge.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:xe.MatList,selector:"mat-list",exportAs:["matList"]},{kind:"component",type:xe.MatListItem,selector:"mat-list-item, a[mat-list-item], button[mat-list-item]",inputs:["activated"],exportAs:["matListItem"]},{kind:"directive",type:be.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:be.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:be.CdkDragHandle,selector:"[cdkDragHandle]",inputs:["cdkDragHandleDisabled"]},{kind:"directive",type:_.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:_.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:_.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:_.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:ye.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:nn,decorators:[{type:n,args:[{selector:"tb-arguments-map-config",providers:[{provide:w,useExisting:a((()=>nn)),multi:!0},{provide:P,useExisting:a((()=>nn)),multi:!0}],template:'
\n\n
\n \n \n
\n \n
\n {{argumentControl.get(\'name\').value}}.\n
\n
\n \n tb.rulenode.argument-type-field-input\n \n \n {{ argumentTypeResultMap.get(argument) | translate }}\n \n \n \n tb.rulenode.argument-type-field-input-required\n \n \n \n tb.rulenode.argument-key-field-input\n \n help\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
\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 tb.rulenode.default-value-field-input\n \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:G.Store},{type:j.TranslateService},{type:t.Injector},{type:E.FormBuilder}]},propDecorators:{disabled:[{type:i}],function:[{type:i}]}});class rn extends b{get required(){return this.requiredValue}set required(e){this.requiredValue=ce(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=[...Et.values()],this.propagateChange=null}ngOnInit(){this.mathFunctionForm=this.fb.group({operation:[""]}),this.filteredOptions=this.mathFunctionForm.get("operation").valueChanges.pipe(he((e=>{let t;t="string"==typeof e&&Gt[e]?Gt[e]:null,this.updateView(t)})),Ce((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=Et.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",rn),rn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:rn,deps:[{token:G.Store},{token:j.TranslateService},{token:t.Injector},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),rn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:rn,selector:"tb-math-function-autocomplete",inputs:{required:"required",disabled:"disabled"},providers:[{provide:w,useExisting:a((()=>rn)),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:oe.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Te.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Te.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Ie.HighlightPipe,name:"highlight"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:rn,decorators:[{type:n,args:[{selector:"tb-math-function-autocomplete",providers:[{provide:w,useExisting:a((()=>rn)),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:G.Store},{type:j.TranslateService},{type:t.Injector},{type:E.UntypedFormBuilder}]},propDecorators:{required:[{type:i}],disabled:[{type:i}],operationInput:[{type:o,args:["operationInput",{static:!0}]}]}});class on extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.MathFunction=Gt,this.ArgumentTypeResult=Vt,this.argumentTypeResultMap=Rt,this.attributeScopeMap=Bt,this.argumentsResult=Object.values(Vt),this.attributeScopeResult=Object.values(Kt)}configForm(){return this.mathFunctionConfigForm}onConfigurationSet(e){this.mathFunctionConfigForm=this.fb.group({operation:[e?e.operation:null,[D.required]],arguments:[e?e.arguments:null,[D.required]],customFunction:[e?e.customFunction:"",[D.required]],result:this.fb.group({type:[e?e.result.type:null,[D.required]],attributeScope:[e?e.result.attributeScope:null,[D.required]],key:[e?e.result.key:"",[D.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===Gt.CUSTOM?this.mathFunctionConfigForm.get("customFunction").enable({emitEvent:!1}):this.mathFunctionConfigForm.get("customFunction").disable({emitEvent:!1}),n===Vt.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",on),on.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:on,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),on.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:on,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 \n \n
\n
\n tb.rulenode.result-title\n
\n
\n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(argument) | translate }}\n \n \n \n tb.rulenode.type-field-input-required\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 \n {{\'tb.rulenode.add-to-body-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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:z.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ge.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:_.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:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:E.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:nn,selector:"tb-arguments-map-config",inputs:["disabled","function"]},{kind:"component",type:rn,selector:"tb-math-function-autocomplete",inputs:["required","disabled"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:on,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 \n \n
\n
\n tb.rulenode.result-title\n
\n
\n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(argument) | translate }}\n \n \n \n tb.rulenode.type-field-input-required\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 \n {{\'tb.rulenode.add-to-body-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:G.Store},{type:E.UntypedFormBuilder}]}});class an{constructor(e,t){this.store=e,this.fb=t,this.subscriptSizing="fixed",this.searchText="",this.dirty=!1,this.messageTypes=["POST_ATTRIBUTES_REQUEST","POST_TELEMETRY_REQUEST"],this.propagateChange=e=>{},this.messageTypeFormGroup=this.fb.group({messageType:[null,[D.required,D.maxLength(255)]]})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnInit(){this.outputMessageTypes=this.messageTypeFormGroup.get("messageType").valueChanges.pipe(he((e=>{this.updateView(e)})),Ce((e=>e||"")),ve((e=>this.fetchMessageTypes(e))))}writeValue(e){this.searchText="",this.modelValue=e,this.messageTypeFormGroup.get("messageType").patchValue(e,{emitEvent:!1}),this.dirty=!0}onFocus(){this.dirty&&(this.messageTypeFormGroup.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0}),this.dirty=!1)}updateView(e){this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}displayMessageTypeFn(e){return e||void 0}fetchMessageTypes(e,t=!1){return this.searchText=e,Ne(this.messageTypes).pipe(Ce((n=>n.filter((n=>t?!!e&&n===e:!e||n.toUpperCase().startsWith(e.toUpperCase()))))))}clear(){this.messageTypeFormGroup.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.messageTypeInput.nativeElement.blur(),this.messageTypeInput.nativeElement.focus()}),0)}}e("OutputMessageTypeAutocompleteComponent",an),an.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:an,deps:[{token:G.Store},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),an.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:an,selector:"tb-output-message-type-autocomplete",inputs:{autocompleteHint:"autocompleteHint",subscriptSizing:"subscriptSizing"},providers:[{provide:w,useExisting:a((()=>an)),multi:!0}],viewQueries:[{propertyName:"messageTypeInput",first:!0,predicate:["messageTypeInput"],descendants:!0,static:!0}],ngImport:t,template:'\n \n \n \n \n {{msgType}}\n \n \n {{autocompleteHint | translate}}\n \n {{ \'tb.rulenode.output-message-type-required\' | translate }}\n \n \n {{ \'tb.rulenode.output-message-type-max-length\' | 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:oe.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:z.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Te.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Te.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:an,decorators:[{type:n,args:[{selector:"tb-output-message-type-autocomplete",providers:[{provide:w,useExisting:a((()=>an)),multi:!0}],template:'\n \n \n \n \n {{msgType}}\n \n \n {{autocompleteHint | translate}}\n \n {{ \'tb.rulenode.output-message-type-required\' | translate }}\n \n \n {{ \'tb.rulenode.output-message-type-max-length\' | translate }}\n \n\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.FormBuilder}]},propDecorators:{messageTypeInput:[{type:o,args:["messageTypeInput",{static:!0}]}],autocompleteHint:[{type:i}],subscriptSizing:[{type:i}]}});class ln extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.destroy$=new Se,this.serviceType=p.TB_RULE_ENGINE,this.deduplicationStrategie=gt,this.deduplicationStrategies=Object.keys(this.deduplicationStrategie),this.deduplicationStrategiesTranslations=yt}configForm(){return this.deduplicationConfigForm}onConfigurationSet(e){this.deduplicationConfigForm=this.fb.group({interval:[Z(e?.interval)?e.interval:null,[D.required,D.min(1)]],strategy:[Z(e?.strategy)?e.strategy:null,[D.required]],outMsgType:[Z(e?.outMsgType)?e.outMsgType:null,[D.required]],queueName:[Z(e?.queueName)?e.queueName:null,[D.required]],maxPendingMsgs:[Z(e?.maxPendingMsgs)?e.maxPendingMsgs:null,[D.required,D.min(1),D.max(1e3)]],maxRetries:[Z(e?.maxRetries)?e.maxRetries:null,[D.required,D.min(0),D.max(100)]]}),this.deduplicationConfigForm.get("strategy").valueChanges.pipe(Fe(this.destroy$)).subscribe((e=>{this.enableControl(e)}))}updateValidators(e){this.enableControl(this.deduplicationConfigForm.get("strategy").value)}validatorTriggers(){return["strategy"]}enableControl(e){e===this.deduplicationStrategie.ALL?(this.deduplicationConfigForm.get("outMsgType").enable({emitEvent:!1}),this.deduplicationConfigForm.get("queueName").enable({emitEvent:!1})):(this.deduplicationConfigForm.get("outMsgType").disable({emitEvent:!1}),this.deduplicationConfigForm.get("queueName").disable({emitEvent:!1}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}}e("DeduplicationConfigComponent",ln),ln.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ln,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ln.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:ln,selector:"tb-action-node-msg-deduplication-config",usesInheritance:!0,ngImport:t,template:"
\n \n {{'tb.rulenode.interval' | translate}}\n \n {{'tb.rulenode.interval-hint' | translate}}\n \n {{'tb.rulenode.interval-required' | translate}}\n \n \n {{'tb.rulenode.interval-min-error' | translate}}\n \n \n \n {{'tb.rulenode.strategy' | translate}}\n \n \n {{ deduplicationStrategiesTranslations.get(strategy) | translate }}\n \n \n \n {{'tb.rulenode.strategy-first-hint' | translate}}\n {{'tb.rulenode.strategy-last-hint' | translate}}\n \n {{'tb.rulenode.strategy-required' | translate}}\n \n \n
\n \n \n \n \n
\n \n \n \n
\n
Advanced settings
\n
\n
\n
\n \n \n {{'tb.rulenode.max-pending-msgs' | translate}}\n \n {{'tb.rulenode.max-pending-msgs-hint' | translate}}\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 \n \n {{'tb.rulenode.max-retries' | translate}}\n \n {{'tb.rulenode.max-retries-hint' | translate}}\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 \n \n
\n
\n",styles:[":host ::ng-deep .mat-expansion-panel.advanced-settings{border:none;box-shadow:none;padding:0}:host ::ng-deep .mat-expansion-panel.advanced-settings .mat-expansion-panel-body{padding:0}:host ::ng-deep .mat-expansion-panel:not(.mat-expanded) .mat-expansion-panel-header:not([aria-disabled=true]):hover{background:white}\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.QueueAutocompleteComponent,selector:"tb-queue-autocomplete",inputs:["labelText","requiredText","autocompleteHint","subscriptSizing","required","queueType","disabled"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:qe.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:qe.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:qe.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:qe.MatExpansionPanelContent,selector:"ng-template[matExpansionPanelContent]"},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:an,selector:"tb-output-message-type-autocomplete",inputs:["autocompleteHint","subscriptSizing"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ln,decorators:[{type:n,args:[{selector:"tb-action-node-msg-deduplication-config",template:"
\n \n {{'tb.rulenode.interval' | translate}}\n \n {{'tb.rulenode.interval-hint' | translate}}\n \n {{'tb.rulenode.interval-required' | translate}}\n \n \n {{'tb.rulenode.interval-min-error' | translate}}\n \n \n \n {{'tb.rulenode.strategy' | translate}}\n \n \n {{ deduplicationStrategiesTranslations.get(strategy) | translate }}\n \n \n \n {{'tb.rulenode.strategy-first-hint' | translate}}\n {{'tb.rulenode.strategy-last-hint' | translate}}\n \n {{'tb.rulenode.strategy-required' | translate}}\n \n \n
\n \n \n \n \n
\n \n \n \n
\n
Advanced settings
\n
\n
\n
\n \n \n {{'tb.rulenode.max-pending-msgs' | translate}}\n \n {{'tb.rulenode.max-pending-msgs-hint' | translate}}\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 \n \n {{'tb.rulenode.max-retries' | translate}}\n \n {{'tb.rulenode.max-retries-hint' | translate}}\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 \n \n
\n
\n",styles:[":host ::ng-deep .mat-expansion-panel.advanced-settings{border:none;box-shadow:none;padding:0}:host ::ng-deep .mat-expansion-panel.advanced-settings .mat-expansion-panel-body{padding:0}:host ::ng-deep .mat-expansion-panel:not(.mat-expanded) .mat-expansion-panel-header:not([aria-disabled=true]):hover{background:white}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class sn extends b{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,this.disabled=!1,this.uniqueKeyValuePairValidator=!1,this.required=!1}ngOnInit(){this.ngControl=this.injector.get(V),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,[D.required,D.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:[e[n],[D.required,D.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]}));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:["",[D.required,D.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:["",[D.required,D.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]}))}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("KvMapConfigComponent",sn),sn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:sn,deps:[{token:G.Store},{token:j.TranslateService},{token:t.Injector},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),sn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",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:w,useExisting:a((()=>sn)),multi:!0},{provide:P,useExisting:a((()=>sn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n \n
\n
\n
\n
\n \n {{ keyText }}\n \n \n {{ keyRequiredText }}\n \n \n arrow_forward\n \n {{ valText }}\n \n \n {{ valRequiredText }}\n \n \n
\n \n
\n
\n
\n {{ \'tb.rulenode.kv-map-pattern-hint\' | translate }}\n \n
\n
\n
\n \n \n \n
\n \n
\n
\n',styles:[":host ::ng-deep{width:100%}:host ::ng-deep .tb-kv-map-config{margin-bottom:12px}:host ::ng-deep .tb-kv-map-config .map-label{font-weight:400;font-size:12px;color:#3d3d3d;letter-spacing:.25px}:host ::ng-deep .tb-kv-map-config .body{margin-top:7px;max-height:363px;overflow:auto}:host ::ng-deep .tb-kv-map-config .body .mapping-block{margin-bottom:15px}:host ::ng-deep .tb-kv-map-config .body .mapping-block .inputs-block{border:1px solid #E0E0E0;width:100%;border-radius:6px;padding:22px 22px 0;align-items:center}:host ::ng-deep .tb-kv-map-config .body .mapping-block .inputs-block .arrow-icon{width:24px;height:24px;line-height:24px;font-size:24px;margin:0 2px 22px;color:#9e9e9e}:host ::ng-deep .tb-kv-map-config tb-error{display:block;margin-top:-12px;margin-bottom:8px}\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:Ge.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"]},{kind:"component",type:fe.TbErrorComponent,selector:"tb-error",inputs:["error"]},{kind:"component",type:oe.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:oe.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ge.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:_.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:_.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:_.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:_.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:ye.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),Ae([h()],sn.prototype,"disabled",void 0),Ae([h()],sn.prototype,"uniqueKeyValuePairValidator",void 0),Ae([h()],sn.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:sn,decorators:[{type:n,args:[{selector:"tb-kv-map-config",providers:[{provide:w,useExisting:a((()=>sn)),multi:!0},{provide:P,useExisting:a((()=>sn)),multi:!0}],template:'
\n
\n \n
\n
\n
\n
\n \n {{ keyText }}\n \n \n {{ keyRequiredText }}\n \n \n arrow_forward\n \n {{ valText }}\n \n \n {{ valRequiredText }}\n \n \n
\n \n
\n
\n
\n {{ \'tb.rulenode.kv-map-pattern-hint\' | translate }}\n \n
\n
\n
\n \n \n \n
\n \n
\n
\n',styles:[":host ::ng-deep{width:100%}:host ::ng-deep .tb-kv-map-config{margin-bottom:12px}:host ::ng-deep .tb-kv-map-config .map-label{font-weight:400;font-size:12px;color:#3d3d3d;letter-spacing:.25px}:host ::ng-deep .tb-kv-map-config .body{margin-top:7px;max-height:363px;overflow:auto}:host ::ng-deep .tb-kv-map-config .body .mapping-block{margin-bottom:15px}:host ::ng-deep .tb-kv-map-config .body .mapping-block .inputs-block{border:1px solid #E0E0E0;width:100%;border-radius:6px;padding:22px 22px 0;align-items:center}:host ::ng-deep .tb-kv-map-config .body .mapping-block .inputs-block .arrow-icon{width:24px;height:24px;line-height:24px;font-size:24px;margin:0 2px 22px;color:#9e9e9e}:host ::ng-deep .tb-kv-map-config tb-error{display:block;margin-top:-12px;margin-bottom:8px}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:j.TranslateService},{type:t.Injector},{type:E.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{constructor(e,t){this.store=e,this.fb=t,this.destroy$=new Se}ngOnInit(){this.slideToggleControlGroup=this.fb.group({slideToggleControl:[null,[]]}),this.slideToggleControlGroup.get("slideToggleControl").valueChanges.pipe(Fe(this.destroy$)).subscribe((e=>{this.propagateChange(e)}))}writeValue(e){this.slideToggleControlGroup.get("slideToggleControl").patchValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){e?this.slideToggleControlGroup.disable({emitEvent:!1}):this.slideToggleControlGroup.enable({emitEvent:!1})}ngOnDestroy(){this.destroy$.next(null),this.destroy$.complete()}}e("SlideToggleComponent",mn),mn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:mn,deps:[{token:G.Store},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),mn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:mn,selector:"tb-slide-toggle",inputs:{slideToggleName:"slideToggleName",slideToggleTooltip:"slideToggleTooltip"},providers:[{provide:w,useExisting:a((()=>mn)),multi:!0}],ngImport:t,template:'
\n \n {{ slideToggleName }}\n \n info\n
\n',styles:[":host ::ng-deep .slide-toggle-container{align-items:center}:host ::ng-deep .slide-toggle-container .slide-toggle{margin-right:8px}:host ::ng-deep .slide-toggle-container .slide-toggle label{padding-left:12px}:host ::ng-deep .slide-toggle-container .tooltip-icon{width:18px;height:18px;line-height:18px;font-size:18px;color:#e0e0e0}:host ::ng-deep .slide-toggle-container .tooltip-icon:hover{color:#9e9e9e}\n"],dependencies:[{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ge.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:Ve.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:mn,decorators:[{type:n,args:[{selector:"tb-slide-toggle",providers:[{provide:w,useExisting:a((()=>mn)),multi:!0}],template:'
\n \n {{ slideToggleName }}\n \n info\n
\n',styles:[":host ::ng-deep .slide-toggle-container{align-items:center}:host ::ng-deep .slide-toggle-container .slide-toggle{margin-right:8px}:host ::ng-deep .slide-toggle-container .slide-toggle label{padding-left:12px}:host ::ng-deep .slide-toggle-container .tooltip-icon{width:18px;height:18px;line-height:18px;font-size:18px;color:#e0e0e0}:host ::ng-deep .slide-toggle-container .tooltip-icon:hover{color:#9e9e9e}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:E.FormBuilder}]},propDecorators:{slideToggleName:[{type:i}],slideToggleTooltip:[{type:i}]}});class un extends b{get required(){return this.requiredValue}set required(e){this.requiredValue=ce(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(g),this.directionTypeTranslations=y,this.entityType=x,this.propagateChange=null}ngOnInit(){this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[D.required]],maxLevel:[null,[D.min(1)]],relationType:[null],deviceTypes:[null,[D.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",un),un.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:un,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),un.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:un,selector:"tb-device-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:w,useExisting:a((()=>un)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\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 {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n \n \n \n \n \n \n
\n',styles:[":host .relation-level{margin-bottom:16px}:host .last-level-slide-toggle{margin:8px 0 24px}:host .relation-type-autocomplete{margin-bottom:16px}\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:Ee.EntitySubTypeListComponent,selector:"tb-entity-subtype-list",inputs:["label","required","disabled","entityType"]},{kind:"component",type:De.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["label","floatLabel","required","disabled","subscriptSizing"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:mn,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:un,decorators:[{type:n,args:[{selector:"tb-device-relations-query-config",providers:[{provide:w,useExisting:a((()=>un)),multi:!0}],template:'
\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 {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n \n \n \n \n \n \n
\n',styles:[":host .relation-level{margin-bottom:16px}:host .last-level-slide-toggle{margin:8px 0 24px}:host .relation-type-autocomplete{margin-bottom:16px}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class pn{constructor(){this.required=!1}}e("FieldsetComponent",pn),pn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:pn,deps:[],target:t.ɵɵFactoryTarget.Component}),pn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:pn,selector:"tb-fieldset-component",inputs:{label:"label",required:"required"},ngImport:t,template:'
\n {{ label }}{{ required ? \'*\' : \'\' }}\n
\n \n
\n
\n',styles:[".fields-group{padding:0 16px;margin:0;border:1px solid #E0E0E0;border-radius:4px}.fields-group .fieldset-content{align-items:center}.fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}.fields-group legend{color:#757575;width:-moz-fit-content;width:fit-content;margin-bottom:4px}.fields-group legend+*{display:block}.fields-group legend+*.no-margin-top{margin-top:0}\n"],dependencies:[{kind:"directive",type:_.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"]}]}),Ae([h()],pn.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:pn,decorators:[{type:n,args:[{selector:"tb-fieldset-component",template:'
\n {{ label }}{{ required ? \'*\' : \'\' }}\n
\n \n
\n
\n',styles:[".fields-group{padding:0 16px;margin:0;border:1px solid #E0E0E0;border-radius:4px}.fields-group .fieldset-content{align-items:center}.fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}.fields-group legend{color:#757575;width:-moz-fit-content;width:fit-content;margin-bottom:4px}.fields-group legend+*{display:block}.fields-group legend+*.no-margin-top{margin-top:0}\n"]}]}],propDecorators:{label:[{type:i}],required:[{type:i}]}});class dn extends b{get required(){return this.requiredValue}set required(e){this.requiredValue=ce(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(g),this.directionTypeTranslations=y,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[D.required]],maxLevel:[null,[D.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",dn),dn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:dn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),dn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:dn,selector:"tb-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:w,useExisting:a((()=>dn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n \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 {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n
\n \n \n \n
\n \n
\n
\n
\n',styles:[":host .map-label{font-weight:400;font-size:12px;color:#3d3d3d;letter-spacing:.25px}:host .last-level-slide-toggle{margin-bottom:18px;display:inline-block}\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:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:we.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]},{kind:"component",type:mn,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"component",type:pn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:dn,decorators:[{type:n,args:[{selector:"tb-relations-query-config",providers:[{provide:w,useExisting:a((()=>dn)),multi:!0}],template:'
\n \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 {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n
\n \n \n \n
\n \n
\n
\n
\n',styles:[":host .map-label{font-weight:400;font-size:12px;color:#3d3d3d;letter-spacing:.25px}:host .last-level-slide-toggle{margin-bottom:18px;display:inline-block}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class cn extends b{get required(){return this.requiredValue}set required(e){this.requiredValue=ce(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.truncate=n,this.fb=r,this.placeholder="tb.rulenode.message-type",this.separatorKeysCodes=[ie,le,se],this.messageTypes=[],this.messageTypesList=[],this.searchText="",this.propagateChange=e=>{},this.messageTypeConfigForm=this.fb.group({messageType:[null]});for(const e of Object.keys(C))this.messageTypesList.push({name:v.get(C[e]),value:e})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnInit(){this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(Le(""),Ce((e=>e||"")),ve((e=>this.fetchMessageTypes(e))),ke())}ngAfterViewInit(){}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&&null!=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 Ne(this.messageTypesList.filter((t=>t.name.toUpperCase().includes(e))))}return Ne(this.messageTypesList)}transformMessageType(e){if((e||"").trim()){let t=null;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",cn),cn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:cn,deps:[{token:G.Store},{token:j.TranslateService},{token:F.TruncatePipe},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),cn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:cn,selector:"tb-message-types-config",inputs:{required:"required",label:"label",placeholder:"placeholder",disabled:"disabled"},providers:[{provide:w,useExisting:a((()=>cn)),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 \n \n {{ \'tb.rulenode.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:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Te.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Te.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:Te.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{kind:"component",type:ue.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:ue.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:ue.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:ue.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Ie.HighlightPipe,name:"highlight"},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:cn,decorators:[{type:n,args:[{selector:"tb-message-types-config",providers:[{provide:w,useExisting:a((()=>cn)),multi:!0}],template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n \n \n \n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:j.TranslateService},{type:F.TruncatePipe},{type:E.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 fn extends b{get required(){return this.requiredValue}set required(e){this.requiredValue=ce(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[],this.disableCertPemCredentials=!1,this.passwordFieldRequired=!0,this.allCredentialsTypes=Tt,this.credentialsTypeTranslationsMap=It,this.propagateChange=e=>{}}ngOnInit(){this.credentialsConfigFormGroup=this.fb.group({type:[null,[D.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){Z(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([D.required]),this.credentialsConfigFormGroup.get("password").setValidators(this.passwordFieldRequired?[D.required]:[]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(D.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",fn),fn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:fn,deps:[{token:G.Store},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),fn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:fn,selector:"tb-credentials-config",inputs:{required:"required",disableCertPemCredentials:"disableCertPemCredentials",passwordFieldRequired:"passwordFieldRequired"},providers:[{provide:w,useExisting:a((()=>fn)),multi:!0},{provide:P,useExisting:a((()=>fn)),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:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:z.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:qe.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:qe.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:qe.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:qe.MatExpansionPanelDescription,selector:"mat-panel-description"},{kind:"directive",type:qe.MatExpansionPanelContent,selector:"ng-template[matExpansionPanelContent]"},{kind:"directive",type:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Pe.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Re.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:fn,decorators:[{type:n,args:[{selector:"tb-credentials-config",providers:[{provide:w,useExisting:a((()=>fn)),multi:!0},{provide:P,useExisting:a((()=>fn)),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:G.Store},{type:E.FormBuilder}]},propDecorators:{required:[{type:i}],disableCertPemCredentials:[{type:i}],passwordFieldRequired:[{type:i}]}});class gn{constructor(e,t,n){this.store=e,this.fb=t,this.translate=n,this.destroy$=new Se,this.selectOptions=[];for(const e of Pt.keys())this.selectOptions.push({value:e,name:this.translate.instant(Pt.get(e))})}ngOnInit(){this.chipControlGroup=this.fb.group({chipControl:[null,[]]}),this.chipControlGroup.get("chipControl").valueChanges.pipe(Fe(this.destroy$)).subscribe((e=>{e&&this.propagateChange(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})}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}}e("MsgMetadataChipComponent",gn),gn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:gn,deps:[{token:G.Store},{token:E.FormBuilder},{token:j.TranslateService}],target:t.ɵɵFactoryTarget.Component}),gn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:gn,selector:"tb-msg-metadata-chip",inputs:{labelText:"labelText"},providers:[{provide:w,useExisting:a((()=>gn)),multi:!0}],ngImport:t,template:'
\n \n \n {{ option.name }}\n \n
\n',styles:[":host{width:100%}:host .chip-label{font-weight:400;font-size:12px;letter-spacing:.25px;color:#3d3d3d}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:ue.MatChipListbox,selector:"mat-chip-listbox",inputs:["tabIndex","multiple","aria-orientation","selectable","compareWith","required","hideSingleSelectionIndicator","value"],outputs:["change"]},{kind:"component",type:ue.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:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:gn,decorators:[{type:n,args:[{selector:"tb-msg-metadata-chip",providers:[{provide:w,useExisting:a((()=>gn)),multi:!0}],template:'
\n \n \n {{ option.name }}\n \n
\n',styles:[":host{width:100%}:host .chip-label{font-weight:400;font-size:12px;letter-spacing:.25px;color:#3d3d3d}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:E.FormBuilder},{type:j.TranslateService}]},propDecorators:{labelText:[{type:i}]}});class yn extends b{constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.destroy$=new Se,this.sourceFieldSubcritption=[],this.propagateChange=null,this.valueChangeSubscription=null,this.disabled=!1,this.required=!1}ngOnInit(){this.ngControl=this.injector.get(V),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.svListFormGroup=this.fb.group({}),this.svListFormGroup.addControl("keyVals",this.fb.array([]))}keyValsFormArray(){return this.svListFormGroup.get("keyVals")}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}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){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,[D.required]],value:[e[n],[D.required,D.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]}));this.svListFormGroup.setControl("keyVals",this.fb.array(t));for(const e of this.keyValsFormArray().controls)this.keyChangeSubscribe(e);this.valueChangeSubscription=this.svListFormGroup.valueChanges.pipe(Fe(this.destroy$)).subscribe((e=>{this.updateModel()}))}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)Z(t.find((e=>e.value===r.value)))&&r.value!==e?.get("key").value||n.push(r);return n}removeKeyVal(e){this.svListFormGroup.get("keyVals").removeAt(e),this.sourceFieldSubcritption[e].unsubscribe(),this.sourceFieldSubcritption.splice(e,1)}addKeyVal(){const e=this.svListFormGroup.get("keyVals");e.push(this.fb.group({key:["",[D.required]],value:["",[D.required,D.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]})),this.keyChangeSubscribe(e.controls[e.length-1])}keyChangeSubscribe(e){this.sourceFieldSubcritption.push(e.get("key").valueChanges.pipe(Fe(this.destroy$)).subscribe((t=>{e.get("value").patchValue(this.targetKeyPrefix+t[0].toUpperCase()+t.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",yn),yn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:yn,deps:[{token:G.Store},{token:j.TranslateService},{token:t.Injector},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),yn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:yn,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:w,useExisting:a((()=>yn)),multi:!0},{provide:P,useExisting:a((()=>yn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n \n
\n
\n
\n
\n \n {{ selectText }}\n \n \n {{option.name}}\n \n \n \n {{ selectRequiredText }}\n \n \n arrow_forward\n \n {{ valText }}\n \n \n {{ valRequiredText }}\n \n \n
\n \n
\n
\n
\n {{ hintText }}\n \n
\n
\n \n \n
\n \n
\n',styles:[":host ::ng-deep{width:100%}:host ::ng-deep .tb-sv-map-config{margin-bottom:12px}:host ::ng-deep .tb-sv-map-config .map-label{font-weight:400;font-size:12px;color:#3d3d3d;letter-spacing:.25px}:host ::ng-deep .tb-sv-map-config .body{max-height:363px;overflow:auto;margin-top:7px}:host ::ng-deep .tb-sv-map-config .body .mapping-block{margin-bottom:15px}:host ::ng-deep .tb-sv-map-config .body .mapping-block .inputs-block{border:1px solid #E0E0E0;width:100%;border-radius:6px;padding:22px 22px 0;align-items:center}:host ::ng-deep .tb-sv-map-config .body .mapping-block .inputs-block .arrow-icon{width:24px;height:24px;line-height:24px;font-size:24px;margin:0 2px 22px;color:#9e9e9e}:host ::ng-deep .tb-sv-map-config tb-error{display:block;margin-top:-12px;margin-bottom:8px}\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:Ge.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"]},{kind:"component",type:fe.TbErrorComponent,selector:"tb-error",inputs:["error"]},{kind:"component",type:oe.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:oe.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ge.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:_.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:_.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:_.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:_.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:ye.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),Ae([h()],yn.prototype,"disabled",void 0),Ae([h()],yn.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:yn,decorators:[{type:n,args:[{selector:"tb-sv-map-config",providers:[{provide:w,useExisting:a((()=>yn)),multi:!0},{provide:P,useExisting:a((()=>yn)),multi:!0}],template:'
\n
\n \n
\n
\n
\n
\n \n {{ selectText }}\n \n \n {{option.name}}\n \n \n \n {{ selectRequiredText }}\n \n \n arrow_forward\n \n {{ valText }}\n \n \n {{ valRequiredText }}\n \n \n
\n \n
\n
\n
\n {{ hintText }}\n \n
\n
\n \n \n
\n \n
\n',styles:[":host ::ng-deep{width:100%}:host ::ng-deep .tb-sv-map-config{margin-bottom:12px}:host ::ng-deep .tb-sv-map-config .map-label{font-weight:400;font-size:12px;color:#3d3d3d;letter-spacing:.25px}:host ::ng-deep .tb-sv-map-config .body{max-height:363px;overflow:auto;margin-top:7px}:host ::ng-deep .tb-sv-map-config .body .mapping-block{margin-bottom:15px}:host ::ng-deep .tb-sv-map-config .body .mapping-block .inputs-block{border:1px solid #E0E0E0;width:100%;border-radius:6px;padding:22px 22px 0;align-items:center}:host ::ng-deep .tb-sv-map-config .body .mapping-block .inputs-block .arrow-icon{width:24px;height:24px;line-height:24px;font-size:24px;margin:0 2px 22px;color:#9e9e9e}:host ::ng-deep .tb-sv-map-config tb-error{display:block;margin-top:-12px;margin-bottom:8px}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:j.TranslateService},{type:t.Injector},{type:E.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 xn extends b{get required(){return this.requiredValue}set required(e){this.requiredValue=ce(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(g),this.directionTypeTranslations=y,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[D.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",xn),xn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:xn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),xn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:xn,selector:"tb-relations-query-config-old",inputs:{disabled:"disabled",required:"required"},providers:[{provide:w,useExisting:a((()=>xn)),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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:we.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:xn,decorators:[{type:n,args:[{selector:"tb-relations-query-config-old",providers:[{provide:w,useExisting:a((()=>xn)),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:G.Store},{type:E.UntypedFormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class bn{constructor(e,t,n){this.store=e,this.translate=t,this.fb=n,this.destroy$=new Se,this.separatorKeysCodes=[ie,le,se]}ngOnInit(){this.attributeControlGroup=this.fb.group({clientAttributeNames:[null,[]],sharedAttributeNames:[null,[]],serverAttributeNames:[null,[]],latestTsKeyNames:[null,[]],getLatestValueWithTs:[!1,[]]}),this.attributeControlGroup.valueChanges.pipe(Fe(this.destroy$)).subscribe((e=>{this.propagateChange(e)}))}writeValue(e){this.attributeControlGroup.patchValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){e?this.attributeControlGroup.disable({emitEvent:!1}):this.attributeControlGroup.enable({emitEvent:!1})}ngOnDestroy(){this.destroy$.next(null),this.destroy$.complete()}removeKey(e,t){const n=this.attributeControlGroup.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.attributeControlGroup.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.attributeControlGroup.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.attributeControlGroup.get(t).setValue(e,{emitEvent:!0}))}n&&(n.value="")}clearChipGrid(e){this.attributeControlGroup.get(e).patchValue([],{emitEvent:!0})}}e("SelectAttributesComponent",bn),bn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:bn,deps:[{token:G.Store},{token:j.TranslateService},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),bn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:bn,selector:"tb-select-attributes",inputs:{popupHelpLink:"popupHelpLink"},providers:[{provide:w,useExisting:a((()=>bn)),multi:!0}],ngImport:t,template:'
\n \n tb.rulenode.client-attributes\n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.shared-attributes\n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.server-attributes\n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.latest-telemetry\n \n \n {{key}}\n close\n \n \n \n \n \n
\n {{ \'tb.rulenode.kv-map-pattern-hint\' | translate }}\n \n
\n \n \n
\n',styles:[":host ::ng-deep .chip-grid{width:100%;margin-bottom:16px}:host ::ng-deep .fetch-slide-toggle{width:100%;margin-bottom:22px;display:block}\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:Ge.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"]},{kind:"component",type:oe.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ge.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:ue.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:ue.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:ue.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:ue.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:mn,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:bn,decorators:[{type:n,args:[{selector:"tb-select-attributes",providers:[{provide:w,useExisting:a((()=>bn)),multi:!0}],template:'
\n \n tb.rulenode.client-attributes\n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.shared-attributes\n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.server-attributes\n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.latest-telemetry\n \n \n {{key}}\n close\n \n \n \n \n \n
\n {{ \'tb.rulenode.kv-map-pattern-hint\' | translate }}\n \n
\n \n \n
\n',styles:[":host ::ng-deep .chip-grid{width:100%;margin-bottom:16px}:host ::ng-deep .fetch-slide-toggle{width:100%;margin-bottom:22px;display:block}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:j.TranslateService},{type:E.FormBuilder}]},propDecorators:{popupHelpLink:[{type:i}]}});class hn{}e("RulenodeCoreConfigCommonModule",hn),hn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:hn,deps:[],target:t.ɵɵFactoryTarget.NgModule}),hn.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:hn,declarations:[sn,un,dn,cn,fn,Je,nn,rn,an,Wt,gn,mn,yn,pn,xn,bn],imports:[K,L,Me],exports:[sn,un,dn,cn,fn,Je,nn,rn,an,Wt,gn,mn,yn,pn,xn,bn]}),hn.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:hn,imports:[K,L,Me]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:hn,decorators:[{type:l,args:[{declarations:[sn,un,dn,cn,fn,Je,nn,rn,an,Wt,gn,mn,yn,pn,xn,bn],imports:[K,L,Me],exports:[sn,un,dn,cn,fn,Je,nn,rn,an,Wt,gn,mn,yn,pn,xn,bn]}]}]});class Cn{}e("RuleNodeCoreConfigActionModule",Cn),Cn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Cn,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Cn.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:Cn,declarations:[tn,We,Zt,Yt,zt,Ye,Ze,et,tt,jt,nt,ot,Ut,_t,Jt,Xt,en,Xe,rt,Qt,$t,on,ln],imports:[K,L,Me,hn],exports:[tn,We,Zt,Yt,zt,Ye,Ze,et,tt,jt,nt,ot,Ut,_t,Jt,Xt,en,Xe,rt,Qt,$t,on,ln]}),Cn.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Cn,imports:[K,L,Me,hn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Cn,decorators:[{type:l,args:[{declarations:[tn,We,Zt,Yt,zt,Ye,Ze,et,tt,jt,nt,ot,Ut,_t,Jt,Xt,en,Xe,rt,Qt,$t,on,ln],imports:[K,L,Me,hn],exports:[tn,We,Zt,Yt,zt,Ye,Ze,et,tt,jt,nt,ot,Ut,_t,Jt,Xt,en,Xe,rt,Qt,$t,on,ln]}]}]});class vn extends s{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[ie,le,se]}configForm(){return this.calculateDeltaConfigForm}onConfigurationSet(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e.inputValueKey,[D.required,D.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],outputValueKey:[e.outputValueKey,[D.required,D.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],useCache:[e.useCache,[]],addPeriodBetweenMsgs:[e.addPeriodBetweenMsgs,[]],periodValueKey:[e.periodValueKey,[]],round:[e.round,[D.min(0),D.max(15)]],tellFailureIfDeltaIsNegative:[e.tellFailureIfDeltaIsNegative,[]]})}prepareInputConfig(e){return{inputValueKey:Z(e?.inputValueKey)?e.inputValueKey:null,outputValueKey:Z(e?.outputValueKey)?e.outputValueKey:null,useCache:!Z(e?.useCache)||e.useCache,addPeriodBetweenMsgs:!!Z(e?.addPeriodBetweenMsgs)&&e.addPeriodBetweenMsgs,periodValueKey:Z(e?.periodValueKey)?e.periodValueKey:null,round:Z(e?.round)?e.round:null,tellFailureIfDeltaIsNegative:!Z(e?.tellFailureIfDeltaIsNegative)||e.tellFailureIfDeltaIsNegative}}prepareOutputConfig(e){return ee(e)}updateValidators(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([D.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["addPeriodBetweenMsgs"]}}e("CalculateDeltaConfigComponent",vn),vn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:vn,deps:[{token:G.Store},{token:j.TranslateService},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),vn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:vn,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 \n \n \n \n
\n \n {{ 'tb.rulenode.period-value-key' | translate }}\n \n \n {{ 'tb.rulenode.period-value-key-required' | translate }}\n \n \n
\n",styles:[":host ::ng-deep .slide-toggles-block .slide-toggle{margin:12px 0}:host ::ng-deep .period-input{margin-top:20px}\n"],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:mn,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:vn,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 \n \n \n \n
\n \n {{ 'tb.rulenode.period-value-key' | translate }}\n \n \n {{ 'tb.rulenode.period-value-key-required' | translate }}\n \n \n
\n",styles:[":host ::ng-deep .slide-toggles-block .slide-toggle{margin:12px 0}:host ::ng-deep .period-input{margin-top:20px}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:j.TranslateService},{type:E.FormBuilder}]}});class Fn extends s{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=ht;for(const e of Ct.keys())e!==ht.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(Ct.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,ee(e)}toggleChange(e){this.customerAttributesConfigForm.get("dataToFetch").patchValue(e,{emitEvent:!0})}prepareInputConfig(e){let t,n;return t=Z(e?.telemetry)?e.telemetry?ht.LATEST_TELEMETRY:ht.ATTRIBUTES:Z(e?.dataToFetch)?e.dataToFetch:ht.ATTRIBUTES,n=Z(e?.attrMapping)?e.attrMapping:Z(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:Z(e?.fetchTo)?e.fetchTo:wt.METADATA}}selectTranslation(e,t){return this.customerAttributesConfigForm.get("dataToFetch").value===ht.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.customerAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[D.required]],fetchTo:[e.fetchTo]})}}e("CustomerAttributesConfigComponent",Fn),Fn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Fn,deps:[{token:G.Store},{token:E.FormBuilder},{token:j.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Fn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Fn,selector:"tb-enrichment-node-customer-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{margin-bottom:12px;width:420px}\n"],dependencies:[{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Oe.ToggleHeaderComponent,selector:"tb-toggle-header",inputs:["value","options","name","useSelectOnMdLg","ignoreMdLgSize","appearance"],outputs:["valueChange"]},{kind:"component",type:sn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:gn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"component",type:pn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Fn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n \n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{margin-bottom:12px;width:420px}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:E.FormBuilder},{type:j.TranslateService}]}});class Ln extends s{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,[D.required]],tellFailureIfAbsent:[e.tellFailureIfAbsent,[]],fetchTo:[e.fetchTo,[]],attributesControl:[e.attributesControl,[]]})}prepareInputConfig(e){return te(e)&&(e.attributesControl={clientAttributeNames:Z(e?.clientAttributeNames)?e.clientAttributeNames:null,latestTsKeyNames:Z(e?.latestTsKeyNames)?e.latestTsKeyNames:null,serverAttributeNames:Z(e?.serverAttributeNames)?e.serverAttributeNames:null,sharedAttributeNames:Z(e?.sharedAttributeNames)?e.sharedAttributeNames:null,getLatestValueWithTs:!!Z(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{deviceRelationsQuery:Z(e?.deviceRelationsQuery)?e.deviceRelationsQuery:null,tellFailureIfAbsent:!Z(e?.tellFailureIfAbsent)||e.tellFailureIfAbsent,fetchTo:Z(e?.fetchTo)?e.fetchTo:wt.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",Ln),Ln.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Ln,deps:[{token:G.Store},{token:j.TranslateService},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ln.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Ln,selector:"tb-enrichment-node-device-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}:host .device-relations{width:100%}:host .failure-toggle{margin:25px 0}:host .device-attribute{margin-top:12px}\n"],dependencies:[{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:un,selector:"tb-device-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:gn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"component",type:mn,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"component",type:pn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"component",type:bn,selector:"tb-select-attributes",inputs:["popupHelpLink"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Ln,decorators:[{type:n,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n \n \n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}:host .device-relations{width:100%}:host .failure-toggle{margin:25px 0}:host .device-attribute{margin-top:12px}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:j.TranslateService},{type:E.FormBuilder}]}});class kn extends s{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.entityDetailsTranslationsMap=ft,this.entityDetailsList=[],this.searchText="",this.displayDetailsFn=this.displayDetails.bind(this);for(const e of Object.keys(ct))this.entityDetailsList.push(ct[e]);this.detailsFormControl=new R(""),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(Le(""),Ce((e=>e||"")),ve((e=>this.fetchEntityDetails(e))),ke())}ngOnInit(){super.ngOnInit()}configForm(){return this.entityDetailsConfigForm}prepareInputConfig(e){let t;return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),this.detailsList=e?e.detailsList:[],t=Z(e?.addToMetadata)?e.addToMetadata?wt.METADATA:wt.DATA:e?.fetchTo?e.fetchTo:wt.DATA,{detailsList:Z(e?.detailsList)?e.detailsList:null,fetchTo:t}}prepareOutputConfig(e){return e.detailsList=this.detailsList,e}onConfigurationSet(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e.detailsList,[D.required]],fetchTo:[e.fetchTo,[]]}),this.detailsList=e?e.detailsList:[]}displayDetails(e){return e?this.translate.instant(ft.get(e)):void 0}fetchEntityDetails(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ne(this.entityDetailsList.filter((t=>this.translate.instant(ft.get(ct[t])).toUpperCase().includes(e))))}return Ne(this.entityDetailsList)}detailsFieldSelected(e){this.addDetailsField(e.option.value),this.clear("")}removeDetailsField(e){const t=this.detailsList.indexOf(e);t>=0&&(this.detailsList.splice(t,1),this.entityDetailsConfigForm.get("detailsList").setValue(this.detailsList))}addDetailsField(e){this.detailsList||(this.detailsList=[]);-1===this.detailsList.indexOf(e)&&(this.detailsList.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(this.detailsList))}onEntityDetailsInputFocus(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clearChipGrid(){this.detailsList=[],this.entityDetailsConfigForm.get("detailsList").patchValue([],{emitEvent:!0}),setTimeout((()=>{this.detailsInput.nativeElement.blur(),this.detailsInput.nativeElement.focus()}),0)}clear(e=""){this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.detailsInput.nativeElement.blur(),this.detailsInput.nativeElement.focus()}),0)}}e("EntityDetailsConfigComponent",kn),kn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:kn,deps:[{token:G.Store},{token:j.TranslateService},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),kn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:kn,selector:"tb-enrichment-node-entity-details-config",viewQueries:[{propertyName:"detailsInput",first:!0,predicate:["detailsInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.entity-details\' | translate }}\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n \n
\n
\n {{ \'tb.rulenode.no-entity-details-matching\' | translate }}\n
\n
\n
\n
\n {{ \'tb.rulenode.entity-details-list-empty\' | 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:oe.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:z.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ge.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:Te.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Te.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:Te.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{kind:"component",type:ue.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:ue.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:ue.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:ue.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:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:gn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Ie.HighlightPipe,name:"highlight"},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:kn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
\n \n {{ \'tb.rulenode.entity-details\' | translate }}\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n \n
\n
\n {{ \'tb.rulenode.no-entity-details-matching\' | translate }}\n
\n
\n
\n
\n {{ \'tb.rulenode.entity-details-list-empty\' | translate }}\n
\n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:j.TranslateService},{type:E.FormBuilder}]},propDecorators:{detailsInput:[{type:o,args:["detailsInput",{static:!1}]}]}});class Tn extends s{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[ie,le,se],this.aggregationTypes=k,this.aggregations=Object.keys(k),this.aggregationTypesTranslations=T,this.fetchMode=gt,this.samplingOrders=Object.keys(bt),this.samplingOrdersTranslate=Ft,this.timeUnits=Object.values(mt),this.timeUnitsTranslationMap=ut,this.deduplicationStrategiesHintTranslations=xt,this.headerOptions=[],this.timeUnitMap={[mt.MILLISECONDS]:1,[mt.SECONDS]:1e3,[mt.MINUTES]:6e4,[mt.HOURS]:36e5,[mt.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 yt.keys())this.headerOptions.push({value:e,name:this.translate.instant(yt.get(e))})}configForm(){return this.getTelemetryFromDatabaseConfigForm}onConfigurationSet(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e.latestTsKeyNames,[]],aggregation:[e.aggregation,[D.required]],fetchMode:[e.fetchMode,[D.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,ee(e)}prepareInputConfig(e){return te(e)&&(e.interval={startInterval:e.startInterval,startIntervalTimeUnit:e.startIntervalTimeUnit,endInterval:e.endInterval,endIntervalTimeUnit:e.endIntervalTimeUnit}),{latestTsKeyNames:Z(e?.latestTsKeyNames)?e.latestTsKeyNames:null,aggregation:Z(e?.aggregation)?e.aggregation:k.NONE,fetchMode:Z(e?.fetchMode)?e.fetchMode:gt.FIRST,orderBy:Z(e?.orderBy)?e.orderBy:bt.ASC,limit:Z(e?.limit)?e.limit:1e3,useMetadataIntervalPatterns:!!Z(e?.useMetadataIntervalPatterns)&&e.useMetadataIntervalPatterns,interval:{startInterval:Z(e?.interval?.startInterval)?e.interval.startInterval:2,startIntervalTimeUnit:Z(e?.interval?.startIntervalTimeUnit)?e.interval.startIntervalTimeUnit:mt.MINUTES,endInterval:Z(e?.interval?.endInterval)?e.interval.endInterval:1,endIntervalTimeUnit:Z(e?.interval?.endIntervalTimeUnit)?e.interval.endIntervalTimeUnit:mt.MINUTES},startIntervalPattern:Z(e?.startIntervalPattern)?e.startIntervalPattern:null,endIntervalPattern:Z(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([D.required]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([D.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([D.required,D.min(2),D.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([D.required,D.pattern(/(?:.|\s)*\S(&:.|\s)*/)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([D.required,D.pattern(/(?:.|\s)*\S(&:.|\s)*/)])):(this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").setValidators([D.required,D.min(1),D.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").setValidators([D.required]),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").setValidators([D.required,D.min(1),D.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").setValidators([D.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="")}}e("GetTelemetryFromDatabaseConfigComponent",Tn),Tn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Tn,deps:[{token:G.Store},{token:j.TranslateService},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Tn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Tn,selector:"tb-enrichment-node-get-telemetry-from-database",usesInheritance:!0,ngImport:t,template:'
\n \n {{\'tb.rulenode.timeseries-keys\' | translate}}\n \n \n {{key}}\n close\n \n \n \n \n \n {{ "tb.rulenode.general-pattern-hint" | translate }}\n \n \n \n \n\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 {{ \'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 {{ "tb.rulenode.fetch-timeseries-from-to-invalid" | translate }}\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 {{ \'tb.rulenode.metadata-dynamic-interval-hint\' | translate }}\n \n
\n
\n
\n
\n \n
\n \n \n
\n {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get(\'fetchMode\').value) | translate }}\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
\n',styles:[":host ::ng-deep label.tb-title{margin-bottom:-10px}:host ::ng-deep .fetch-interval{margin-top:12px}:host ::ng-deep .fetch-interval .interval-slide-toggle{width:100%;margin:4px 0 16px}:host ::ng-deep .fetch-interval .input-block{width:100%}:host ::ng-deep .interval-description{text-align:center;font-size:12px;color:#3d3d3d;margin-bottom:9px;font-weight:500}:host ::ng-deep .fetch-strategy-fieldset{margin-top:12px}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block{margin-top:8px;align-items:center;width:100%}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block .fetch-mod-toggle{margin-bottom:12px;width:630px}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block .input-block{width:100%}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block .input-block .additional-inputs{margin-bottom:16px}\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:Ge.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"]},{kind:"component",type:oe.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:z.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ge.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:ue.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:ue.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:ue.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:ue.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:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:E.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"component",type:Oe.ToggleHeaderComponent,selector:"tb-toggle-header",inputs:["value","options","name","useSelectOnMdLg","ignoreMdLgSize","appearance"],outputs:["valueChange"]},{kind:"component",type:mn,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"component",type:pn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Tn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n {{\'tb.rulenode.timeseries-keys\' | translate}}\n \n \n {{key}}\n close\n \n \n \n \n \n {{ "tb.rulenode.general-pattern-hint" | translate }}\n \n \n \n \n\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 {{ \'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 {{ "tb.rulenode.fetch-timeseries-from-to-invalid" | translate }}\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 {{ \'tb.rulenode.metadata-dynamic-interval-hint\' | translate }}\n \n
\n
\n
\n
\n \n
\n \n \n
\n {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get(\'fetchMode\').value) | translate }}\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
\n',styles:[":host ::ng-deep label.tb-title{margin-bottom:-10px}:host ::ng-deep .fetch-interval{margin-top:12px}:host ::ng-deep .fetch-interval .interval-slide-toggle{width:100%;margin:4px 0 16px}:host ::ng-deep .fetch-interval .input-block{width:100%}:host ::ng-deep .interval-description{text-align:center;font-size:12px;color:#3d3d3d;margin-bottom:9px;font-weight:500}:host ::ng-deep .fetch-strategy-fieldset{margin-top:12px}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block{margin-top:8px;align-items:center;width:100%}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block .fetch-mod-toggle{margin-bottom:12px;width:630px}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block .input-block{width:100%}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block .input-block .additional-inputs{margin-bottom:16px}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:j.TranslateService},{type:E.FormBuilder}]}});class In extends s{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 te(e)&&(e.attributesControl={clientAttributeNames:Z(e?.clientAttributeNames)?e.clientAttributeNames:null,latestTsKeyNames:Z(e?.latestTsKeyNames)?e.latestTsKeyNames:null,serverAttributeNames:Z(e?.serverAttributeNames)?e.serverAttributeNames:null,sharedAttributeNames:Z(e?.sharedAttributeNames)?e.sharedAttributeNames:null,getLatestValueWithTs:!!Z(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{fetchTo:Z(e?.fetchTo)?e.fetchTo:wt.METADATA,tellFailureIfAbsent:!!Z(e?.tellFailureIfAbsent)&&e.tellFailureIfAbsent,attributesControl:Z(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",In),In.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:In,deps:[{token:G.Store},{token:j.TranslateService},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),In.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:In,selector:"tb-enrichment-node-originator-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}:host .failure-slide-toggle{margin:25px 0}\n"],dependencies:[{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:gn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"component",type:mn,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"component",type:pn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"component",type:bn,selector:"tb-select-attributes",inputs:["popupHelpLink"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:In,decorators:[{type:n,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n \n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}:host .failure-slide-toggle{margin:25px 0}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:j.TranslateService},{type:E.FormBuilder}]}});class Nn extends s{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.originatorFields=[];for(const e of Object.keys(I))this.originatorFields.push({value:I[e].value,name:this.translate.instant(I[e].name)})}configForm(){return this.originatorFieldsConfigForm}prepareOutputConfig(e){return ee(e)}prepareInputConfig(e){return{dataMapping:Z(e?.dataMapping)?e.dataMapping:null,ignoreNullStrings:Z(e?.ignoreNullStrings)?e.ignoreNullStrings:null,fetchTo:Z(e?.fetchTo)?e.fetchTo:wt.METADATA}}onConfigurationSet(e){this.originatorFieldsConfigForm=this.fb.group({dataMapping:[e.dataMapping,[D.required]],ignoreNullStrings:[e.ignoreNullStrings,[]],fetchTo:[e.fetchTo,[]]})}}e("OriginatorFieldsConfigComponent",Nn),Nn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Nn,deps:[{token:G.Store},{token:E.FormBuilder},{token:j.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Nn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Nn,selector:"tb-enrichment-node-originator-fields-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n
\n',styles:[":host .msg-metadata-chip{margin-bottom:12px}:host .skip-slide-toggle{margin-top:20px}\n"],dependencies:[{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:gn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"component",type:mn,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"component",type:yn,selector:"tb-sv-map-config",inputs:["selectOptions","disabled","labelText","requiredText","targetKeyPrefix","selectText","selectRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:pn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Nn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n \n \n \n \n
\n',styles:[":host .msg-metadata-chip{margin-bottom:12px}:host .skip-slide-toggle{margin-top:20px}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:E.FormBuilder},{type:j.TranslateService}]}});class Sn extends s{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.DataToFetch=ht,this.msgMetadataLabelTranslations=vt,this.originatorFields=[],this.fetchToData=[],this.destroy$=new Se,this.defaultKvMap={serialNumber:"sn"},this.defaultSvMap={[I.name.value]:`relatedEntity${this.translate.instant(I.name.name)}`},this.dataToFetchPrevValue="";for(const e of Object.keys(I))this.originatorFields.push({value:I[e].value,name:this.translate.instant(I[e].name)});for(const e of Ct.keys())this.fetchToData.push({value:e,name:this.translate.instant(Ct.get(e))})}toggleChange(e){this.relatedAttributesConfigForm.get("dataToFetch").patchValue(e,{emitEvent:!0})}configForm(){return this.relatedAttributesConfigForm}prepareOutputConfig(e){const t={};for(const n of Object.keys(e.dataMapping))t[n.trim()]=e.dataMapping[n];return e.dataMapping=t,ee(e)}prepareInputConfig(e){let t;return Z(e?.telemetry)?this.dataToFetchPrevValue=e.telemetry?ht.LATEST_TELEMETRY:ht.ATTRIBUTES:this.dataToFetchPrevValue=Z(e?.dataToFetch)?e.dataToFetch:ht.ATTRIBUTES,t=Z(e?.attrMapping)?e.attrMapping:Z(e?.dataMapping)?e.dataMapping:null,{relationsQuery:Z(e?.relationsQuery)?e.relationsQuery:null,dataToFetch:this.dataToFetchPrevValue,dataMapping:t,fetchTo:Z(e?.fetchTo)?e.fetchTo:wt.METADATA}}selectTranslation(e,t){return this.relatedAttributesConfigForm.get("dataToFetch").value===ht.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e.relationsQuery,[D.required]],dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[D.required]],fetchTo:[e.fetchTo,[]]}),this.relatedAttributesConfigForm.get("dataToFetch").valueChanges.pipe(Fe(this.destroy$)).subscribe((e=>{e===ht.FIELDS&&this.relatedAttributesConfigForm.get("dataMapping").patchValue(this.defaultSvMap,{emitEvent:!1}),e!==ht.FIELDS&&this.dataToFetchPrevValue===ht.FIELDS&&this.relatedAttributesConfigForm.get("dataMapping").patchValue(this.defaultKvMap,{emitEvent:!1}),this.dataToFetchPrevValue=e}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}}e("RelatedAttributesConfigComponent",Sn),Sn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Sn,deps:[{token:G.Store},{token:E.FormBuilder},{token:j.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Sn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Sn,selector:"tb-enrichment-node-related-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n',styles:[":host .fetch-data-block{margin-top:12px}:host .fetch-data-block .fetch-to-data-toggle{margin-bottom:12px;width:630px}:host .fetch-data-block .msg-metadata-chip{margin-bottom:12px}\n"],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Oe.ToggleHeaderComponent,selector:"tb-toggle-header",inputs:["value","options","name","useSelectOnMdLg","ignoreMdLgSize","appearance"],outputs:["valueChange"]},{kind:"component",type:sn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:dn,selector:"tb-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:gn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"component",type:yn,selector:"tb-sv-map-config",inputs:["selectOptions","disabled","labelText","requiredText","targetKeyPrefix","selectText","selectRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:pn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Sn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n',styles:[":host .fetch-data-block{margin-top:12px}:host .fetch-data-block .fetch-to-data-toggle{margin-bottom:12px;width:630px}:host .fetch-data-block .msg-metadata-chip{margin-bottom:12px}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:E.FormBuilder},{type:j.TranslateService}]}});class qn extends s{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=ht;for(const e of Ct.keys())e!==ht.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(Ct.get(e))})}configForm(){return this.tenantAttributesConfigForm}toggleChange(e){this.tenantAttributesConfigForm.get("dataToFetch").patchValue(e,{emitEvent:!0})}prepareInputConfig(e){let t,n;return t=Z(e?.telemetry)?e.telemetry?ht.LATEST_TELEMETRY:ht.ATTRIBUTES:Z(e?.dataToFetch)?e.dataToFetch:ht.ATTRIBUTES,n=Z(e?.attrMapping)?e.attrMapping:Z(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:Z(e?.fetchTo)?e.fetchTo:wt.METADATA}}selectTranslation(e,t){return this.tenantAttributesConfigForm.get("dataToFetch").value===ht.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.tenantAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[D.required]],fetchTo:[e.fetchTo,[]]})}}e("TenantAttributesConfigComponent",qn),qn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:qn,deps:[{token:G.Store},{token:E.FormBuilder},{token:j.TranslateService}],target:t.ɵɵFactoryTarget.Component}),qn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:qn,selector:"tb-enrichment-node-tenant-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{margin-bottom:12px;width:420px}:host .msg-metadata-chip{margin-bottom:12px}\n"],dependencies:[{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Oe.ToggleHeaderComponent,selector:"tb-toggle-header",inputs:["value","options","name","useSelectOnMdLg","ignoreMdLgSize","appearance"],outputs:["valueChange"]},{kind:"component",type:sn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:gn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"component",type:pn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:qn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n \n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{margin-bottom:12px;width:420px}:host .msg-metadata-chip{margin-bottom:12px}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:E.FormBuilder},{type:j.TranslateService}]}});class Mn extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.fetchDeviceCredentialsConfigForm}prepareInputConfig(e){return{fetchTo:Z(e?.fetchTo)?e.fetchTo:wt.METADATA}}onConfigurationSet(e){this.fetchDeviceCredentialsConfigForm=this.fb.group({fetchTo:[e.fetchTo,[]]})}}e("FetchDeviceCredentialsConfigComponent",Mn),Mn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Mn,deps:[{token:G.Store},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Mn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Mn,selector:"./tb-enrichment-node-fetch-device-credentials-config",usesInheritance:!0,ngImport:t,template:'
\n \n
\n',dependencies:[{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:gn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Mn,decorators:[{type:n,args:[{selector:"./tb-enrichment-node-fetch-device-credentials-config",template:'
\n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.FormBuilder}]}});class An{}e("RulenodeCoreConfigEnrichmentModule",An),An.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:An,deps:[],target:t.ɵɵFactoryTarget.NgModule}),An.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:An,declarations:[Fn,kn,Ln,In,Nn,Tn,Sn,qn,vn,Mn],imports:[K,L,hn],exports:[Fn,kn,Ln,In,Nn,Tn,Sn,qn,vn,Mn]}),An.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:An,imports:[K,L,hn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:An,decorators:[{type:l,args:[{declarations:[Fn,kn,Ln,In,Nn,Tn,Sn,qn,vn,Mn],imports:[K,L,hn],exports:[Fn,kn,Ln,In,Nn,Tn,Sn,qn,vn,Mn]}]}]});class Gn extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.allAzureIotHubCredentialsTypes=Nt,this.azureIotHubCredentialsTypeTranslationsMap=St}configForm(){return this.azureIotHubConfigForm}onConfigurationSet(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[D.required]],host:[e?e.host:null,[D.required]],port:[e?e.port:null,[D.required,D.min(1),D.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[D.required,D.min(1),D.max(200)]],clientId:[e?e.clientId:null,[D.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[D.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([D.required]);break;case"cert.PEM":t.get("privateKey").setValidators([D.required]),t.get("privateKeyFileName").setValidators([D.required]),t.get("cert").setValidators([D.required]),t.get("certFileName").setValidators([D.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",Gn),Gn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Gn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Gn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Gn,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 \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"],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:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:z.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:qe.MatAccordion,selector:"mat-accordion",inputs:["multi","hideToggle","displayMode","togglePosition"],exportAs:["matAccordion"]},{kind:"component",type:qe.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:qe.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:qe.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:qe.MatExpansionPanelDescription,selector:"mat-panel-description"},{kind:"directive",type:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:E.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"component",type:Pe.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Re.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Gn,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 \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"]}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class En extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.ackValues=["all","-1","0","1"],this.ToByteStandartCharsetTypesValues=Mt,this.ToByteStandartCharsetTypeTranslationMap=At}configForm(){return this.kafkaConfigForm}onConfigurationSet(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[D.required]],keyPattern:[e?e.keyPattern:null],bootstrapServers:[e?e.bootstrapServers:null,[D.required]],retries:[e?e.retries:null,[D.min(0)]],batchSize:[e?e.batchSize:null,[D.min(0)]],linger:[e?e.linger:null,[D.min(0)]],bufferMemory:[e?e.bufferMemory:null,[D.min(0)]],acks:[e?e.acks:null,[D.required]],keySerializer:[e?e.keySerializer:null,[D.required]],valueSerializer:[e?e.valueSerializer:null,[D.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([D.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})}}e("KafkaConfigComponent",En),En.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:En,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),En.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:En,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 \n \n \n tb.rulenode.key-pattern\n \n \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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Wt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:En,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 \n \n \n tb.rulenode.key-pattern\n \n \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:G.Store},{type:E.UntypedFormBuilder}]}});class Dn extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[]}configForm(){return this.mqttConfigForm}onConfigurationSet(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[D.required]],host:[e?e.host:null,[D.required]],port:[e?e.port:null,[D.required,D.min(1),D.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[D.required,D.min(1),D.max(200)]],clientId:[e?e.clientId:null,[]],appendClientIdSuffix:[{value:!!e&&e.appendClientIdSuffix,disabled:!(e&&ne(e.clientId))},[]],cleanSession:[!!e&&e.cleanSession,[]],retainedMessage:[!!e&&e.retainedMessage,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]}),this.subscriptions.push(this.mqttConfigForm.get("clientId").valueChanges.subscribe((e=>{ne(e)?this.mqttConfigForm.get("appendClientIdSuffix").enable({emitEvent:!1}):this.mqttConfigForm.get("appendClientIdSuffix").disable({emitEvent:!1})})))}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}}e("MqttConfigComponent",Dn),Dn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Dn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Dn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Dn,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 \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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fn,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRequired"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Dn,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 \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:G.Store},{type:E.UntypedFormBuilder}]}});class Vn extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.notificationType=N,this.entityType=x}configForm(){return this.notificationConfigForm}onConfigurationSet(e){this.notificationConfigForm=this.fb.group({templateId:[e?e.templateId:null,[D.required]],targets:[e?e.targets:[],[D.required]]})}}e("NotificationConfigComponent",Vn),Vn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Vn,deps:[{token:G.Store},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Vn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Vn,selector:"tb-external-node-notification-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n
\n',dependencies:[{kind:"component",type:He.EntityListComponent,selector:"tb-entity-list",inputs:["entityType","subType","labelText","placeholderText","requiredText","required","disabled","subscriptSizing","hint"]},{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Ke.TemplateAutocompleteComponent,selector:"tb-template-autocomplete",inputs:["required","allowCreate","allowEdit","disabled","notificationTypes"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Vn,decorators:[{type:n,args:[{selector:"tb-external-node-notification-config",template:'
\n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.FormBuilder}]}});class wn extends s{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,[D.required]],topicName:[e?e.topicName:null,[D.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[D.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[D.required]],messageAttributes:[e?e.messageAttributes:null,[]]})}}e("PubSubConfigComponent",wn),wn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:wn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),wn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:wn,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:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Pe.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Wt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:wn,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:G.Store},{type:E.UntypedFormBuilder}]}});class Pn extends s{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,[D.required]],port:[e?e.port:null,[D.required,D.min(1),D.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,[D.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[D.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})}}e("RabbitMqConfigComponent",Pn),Pn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Pn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Pn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Pn,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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:z.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Re.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"component",type:Wt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Pn,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:G.Store},{type:E.UntypedFormBuilder}]}});class Rn extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.proxySchemes=["http","https"],this.httpRequestTypes=Object.keys(qt)}configForm(){return this.restApiCallConfigForm}onConfigurationSet(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[D.required]],requestMethod:[e?e.requestMethod:null,[D.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],trimDoubleQuotes:[!!e&&e.trimDoubleQuotes,[]],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,[D.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?[D.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(r?[D.required,D.min(1),D.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([D.min(0)])),n?this.restApiCallConfigForm.get("maxQueueSize").setValidators([D.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",Rn),Rn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Rn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Rn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Rn,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 \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.trim-double-quotes\' | translate }}\n \n
tb.rulenode.trim-double-quotes-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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fn,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRequired"]},{kind:"component",type:Wt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Rn,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 \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.trim-double-quotes\' | translate }}\n \n
tb.rulenode.trim-double-quotes-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:G.Store},{type:E.UntypedFormBuilder}]}});class On extends s{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([D.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([D.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([D.required,D.min(1),D.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([D.required,D.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(n?[D.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(n?[D.required,D.min(1),D.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",On),On.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:On,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),On.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:On,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:Be.TbCheckboxComponent,selector:"tb-checkbox",inputs:["disabled","trueValue","falseValue"],outputs:["valueChange"]},{kind:"component",type:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:z.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Re.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:On,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:G.Store},{type:E.UntypedFormBuilder}]}});class Hn extends s{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,[D.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[D.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([D.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})}}e("SendSmsConfigComponent",Hn),Hn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Hn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Hn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Hn,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 \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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Ue.SmsProviderConfigurationComponent,selector:"tb-sms-provider-configuration",inputs:["required","disabled"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Hn,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 \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class Kn extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.slackChanelTypes=Object.keys(S),this.slackChanelTypesTranslateMap=q}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,[D.required]],conversationType:[e?e.conversationType:null,[D.required]],conversation:[e?e.conversation:null,[D.required]]})}validatorTriggers(){return["useSystemSettings"]}updateValidators(e){this.slackConfigForm.get("useSystemSettings").value?this.slackConfigForm.get("botToken").clearValidators():this.slackConfigForm.get("botToken").setValidators([D.required]),this.slackConfigForm.get("botToken").updateValueAndValidity({emitEvent:e})}}e("SlackConfigComponent",Kn),Kn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Kn,deps:[{token:G.Store},{token:E.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Kn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Kn,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 \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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ze.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{kind:"component",type:ze.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{kind:"directive",type:_.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:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:_e.SlackConversationAutocompleteComponent,selector:"tb-slack-conversation-autocomplete",inputs:["labelText","requiredText","required","disabled","slackChanelType","token"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Kn,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 \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:G.Store},{type:E.FormBuilder}]}});class Bn extends s{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,[D.required]],accessKeyId:[e?e.accessKeyId:null,[D.required]],secretAccessKey:[e?e.secretAccessKey:null,[D.required]],region:[e?e.region:null,[D.required]]})}}e("SnsConfigComponent",Bn),Bn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Bn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Bn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Bn,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 \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:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Bn,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 \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:G.Store},{type:E.UntypedFormBuilder}]}});class Un extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.sqsQueueType=Lt,this.sqsQueueTypes=Object.keys(Lt),this.sqsQueueTypeTranslationsMap=kt}configForm(){return this.sqsConfigForm}onConfigurationSet(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[D.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[D.required]],delaySeconds:[e?e.delaySeconds:null,[D.min(0),D.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[D.required]],secretAccessKey:[e?e.secretAccessKey:null,[D.required]],region:[e?e.region:null,[D.required]]})}}e("SqsConfigComponent",Un),Un.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Un,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Un.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Un,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 \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:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Wt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Un,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 \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:G.Store},{type:E.UntypedFormBuilder}]}});class zn{}e("RulenodeCoreConfigExternalModule",zn),zn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:zn,deps:[],target:t.ɵɵFactoryTarget.NgModule}),zn.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:zn,declarations:[Bn,Un,wn,En,Dn,Vn,Pn,Rn,On,Gn,Hn,Kn],imports:[K,L,Me,hn],exports:[Bn,Un,wn,En,Dn,Vn,Pn,Rn,On,Gn,Hn,Kn]}),zn.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:zn,imports:[K,L,Me,hn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:zn,decorators:[{type:l,args:[{declarations:[Bn,Un,wn,En,Dn,Vn,Pn,Rn,On,Gn,Hn,Kn],imports:[K,L,Me,hn],exports:[Bn,Un,wn,En,Dn,Vn,Pn,Rn,On,Gn,Hn,Kn]}]}]});class _n extends s{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.alarmStatusTranslationsMap=M,this.alarmStatusList=[],this.searchText="",this.displayStatusFn=this.displayStatus.bind(this);for(const e of Object.keys(A))this.alarmStatusList.push(A[e]);this.statusFormControl=new O(""),this.filteredAlarmStatus=this.statusFormControl.valueChanges.pipe(Le(""),Ce((e=>e||"")),ve((e=>this.fetchAlarmStatus(e))),ke())}ngOnInit(){super.ngOnInit()}configForm(){return this.alarmStatusConfigForm}prepareInputConfig(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e}onConfigurationSet(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[D.required]]})}displayStatus(e){return e?this.translate.instant(M.get(e)):void 0}fetchAlarmStatus(e){const t=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ne(t.filter((t=>this.translate.instant(M.get(A[t])).toUpperCase().includes(e))))}return Ne(t)}alarmStatusSelected(e){this.addAlarmStatus(e.option.value),this.clear("")}removeAlarmStatus(e){const t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){const n=t.indexOf(e);n>=0&&(t.splice(n,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}}addAlarmStatus(e){let t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]);-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}getAlarmStatusList(){return this.alarmStatusList.filter((e=>-1===this.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(e)))}onAlarmStatusInputFocus(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.alarmStatusInput.nativeElement.blur(),this.alarmStatusInput.nativeElement.focus()}),0)}}e("CheckAlarmStatusComponent",_n),_n.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:_n,deps:[{token:G.Store},{token:j.TranslateService},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),_n.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:_n,selector:"tb-filter-node-check-alarm-status-config",viewQueries:[{propertyName:"alarmStatusInput",first:!0,predicate:["alarmStatusInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\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:"component",type:fe.TbErrorComponent,selector:"tb-error",inputs:["error"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Te.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Te.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:Te.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{kind:"component",type:ue.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:ue.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:ue.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:ue.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:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Ie.HighlightPipe,name:"highlight"},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:_n,decorators:[{type:n,args:[{selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:j.TranslateService},{type:E.UntypedFormBuilder}]},propDecorators:{alarmStatusInput:[{type:o,args:["alarmStatusInput",{static:!1}]}]}});class jn extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[ie,le,se]}configForm(){return this.checkMessageConfigForm}onConfigurationSet(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})}validateConfig(){const e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0}removeMessageName(e){const t=this.checkMessageConfigForm.get("messageNames").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))}removeMetadataName(e){const t=this.checkMessageConfigForm.get("metadataNames").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))}addMessageName(e){const t=e.input;let n=e.value;if((n||"").trim()){n=n.trim();let e=this.checkMessageConfigForm.get("messageNames").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.checkMessageConfigForm.get("messageNames").setValue(e,{emitEvent:!0}))}t&&(t.value="")}addMetadataName(e){const t=e.input;let n=e.value;if((n||"").trim()){n=n.trim();let e=this.checkMessageConfigForm.get("metadataNames").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.checkMessageConfigForm.get("metadataNames").setValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("CheckMessageConfigComponent",jn),jn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:jn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),jn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:jn,selector:"tb-filter-node-check-message-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.data-keys\n \n \n {{messageName}}\n close\n \n \n \n tb.rulenode.separator-hint\n \n \n tb.rulenode.metadata-keys\n \n \n {{metadataName}}\n close\n \n \n \n tb.rulenode.separator-hint\n \n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"component",type:ue.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:ue.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:ue.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:ue.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:_.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:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:jn,decorators:[{type:n,args:[{selector:"tb-filter-node-check-message-config",template:'
\n \n tb.rulenode.data-keys\n \n \n {{messageName}}\n close\n \n \n \n tb.rulenode.separator-hint\n \n \n tb.rulenode.metadata-keys\n \n \n {{metadataName}}\n close\n \n \n \n tb.rulenode.separator-hint\n \n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"]}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class $n extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.entitySearchDirection=Object.keys(g),this.entitySearchDirectionTranslationsMap=y}configForm(){return this.checkRelationConfigForm}onConfigurationSet(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[D.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[D.required]:[]],relationType:[e?e.relationType:null,[D.required]]})}validatorTriggers(){return["checkForSingleEntity"]}updateValidators(e){const t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[D.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[D.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})}}e("CheckRelationConfigComponent",$n),$n.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:$n,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),$n.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:$n,selector:"tb-filter-node-check-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\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:"component",type:je.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","appearance","required","disabled"],outputs:["entityChanged"]},{kind:"component",type:pe.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:De.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["label","floatLabel","required","disabled","subscriptSizing"]},{kind:"component",type:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:$n,decorators:[{type:n,args:[{selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class Qn extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=lt,this.perimeterTypes=Object.keys(lt),this.perimeterTypeTranslationMap=st,this.rangeUnits=Object.keys(pt),this.rangeUnitTranslationMap=dt}configForm(){return this.geoFilterConfigForm}onConfigurationSet(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[D.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[D.required]],perimeterType:[e?e.perimeterType:null,[D.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,[]]})}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([D.required]):this.geoFilterConfigForm.get("perimeterKeyName").setValidators([]),t||n!==lt.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([D.required,D.min(-90),D.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([D.required,D.min(-180),D.max(180)]),this.geoFilterConfigForm.get("range").setValidators([D.required,D.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([D.required])),t||n!==lt.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([D.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",Qn),Qn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Qn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Qn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Qn,selector:"tb-filter-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-message-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 \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | 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:B.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:E.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Qn,decorators:[{type:n,args:[{selector:"tb-filter-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-message-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 \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class Jn extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.messageTypeConfigForm}onConfigurationSet(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[D.required]]})}}e("MessageTypeConfigComponent",Jn),Jn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Jn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Jn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Jn,selector:"tb-filter-node-message-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n
\n',dependencies:[{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:cn,selector:"tb-message-types-config",inputs:["required","label","placeholder","disabled"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Jn,decorators:[{type:n,args:[{selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class Yn extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.allowedEntityTypes=[x.DEVICE,x.ASSET,x.ENTITY_VIEW,x.TENANT,x.CUSTOMER,x.USER,x.DASHBOARD,x.RULE_CHAIN,x.RULE_NODE]}configForm(){return this.originatorTypeConfigForm}onConfigurationSet(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[D.required]]})}}e("OriginatorTypeConfigComponent",Yn),Yn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Yn,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Yn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Yn,selector:"tb-filter-node-originator-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n',dependencies:[{kind:"component",type:$e.EntityTypeListComponent,selector:"tb-entity-type-list",inputs:["label","floatLabel","required","disabled","subscriptSizing","allowedEntityTypes","ignoreAuthorityFilter"]},{kind:"directive",type:_.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:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Yn,decorators:[{type:n,args:[{selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class Wn extends s{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.tbelEnabled=W(this.store).tbelEnabled,this.scriptLanguage=d}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:d.JS,[D.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==d.TBEL||this.tbelEnabled||(t=d.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===d.JS?[D.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===d.TBEL?[D.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=d.JS)),e}testScript(){const e=this.scriptConfigForm.get("scriptLang").value,t=e===d.JS?"jsScript":"tbelScript",n=e===d.JS?"rulenode/filter_node_script_fn":"rulenode/tbel/filter_node_script_fn",r=this.scriptConfigForm.get(t).value;this.nodeScriptTestService.testNodeScript(r,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId,n,e).subscribe((e=>{e&&this.scriptConfigForm.get(t).setValue(e)}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===d.JS&&this.jsFuncComponent.validateOnSubmit()}}e("ScriptConfigComponent",Wn),Wn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Wn,deps:[{token:G.Store},{token:E.UntypedFormBuilder},{token:X.NodeScriptTestService},{token:j.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Wn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Wn,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:re.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:oe.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ae.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Wn,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:G.Store},{type:E.UntypedFormBuilder},{type:X.NodeScriptTestService},{type:j.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class Xn extends s{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.tbelEnabled=W(this.store).tbelEnabled,this.scriptLanguage=d}configForm(){return this.switchConfigForm}onConfigurationSet(e){this.switchConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:d.JS,[D.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.switchConfigForm.get("scriptLang").value;t!==d.TBEL||this.tbelEnabled||(t=d.JS,this.switchConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.switchConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.switchConfigForm.get("jsScript").setValidators(t===d.JS?[D.required]:[]),this.switchConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.switchConfigForm.get("tbelScript").setValidators(t===d.TBEL?[D.required]:[]),this.switchConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=d.JS)),e}testScript(){const e=this.switchConfigForm.get("scriptLang").value,t=e===d.JS?"jsScript":"tbelScript",n=e===d.JS?"rulenode/switch_node_script_fn":"rulenode/tbel/switch_node_script_fn",r=this.switchConfigForm.get(t).value;this.nodeScriptTestService.testNodeScript(r,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId,n,e).subscribe((e=>{e&&this.switchConfigForm.get(t).setValue(e)}))}onValidate(){this.switchConfigForm.get("scriptLang").value===d.JS&&this.jsFuncComponent.validateOnSubmit()}}e("SwitchConfigComponent",Xn),Xn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Xn,deps:[{token:G.Store},{token:E.UntypedFormBuilder},{token:X.NodeScriptTestService},{token:j.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Xn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Xn,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:re.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:oe.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ae.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Xn,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:G.Store},{type:E.UntypedFormBuilder},{type:X.NodeScriptTestService},{type:j.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class Zn{}e("RuleNodeCoreConfigFilterModule",Zn),Zn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Zn,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Zn.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:Zn,declarations:[jn,$n,Qn,Jn,Yn,Wn,Xn,_n],imports:[K,L,hn],exports:[jn,$n,Qn,Jn,Yn,Wn,Xn,_n]}),Zn.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Zn,imports:[K,L,hn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Zn,decorators:[{type:l,args:[{declarations:[jn,$n,Qn,Jn,Yn,Wn,Xn,_n],imports:[K,L,hn],exports:[jn,$n,Qn,Jn,Yn,Wn,Xn,_n]}]}]});class er extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.originatorSource=at,this.originatorSources=Object.keys(at),this.originatorSourceTranslationMap=it,this.allowedEntityTypes=[x.DEVICE,x.ASSET,x.ENTITY_VIEW,x.USER,x.EDGE]}configForm(){return this.changeOriginatorConfigForm}onConfigurationSet(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[D.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===at.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([D.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),t===at.ENTITY?(this.changeOriginatorConfigForm.get("entityType").setValidators([D.required]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([D.required,D.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",er),er.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:er,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),er.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:er,selector:"tb-transformation-node-change-originator-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | 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 \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:"component",type:pe.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:_.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:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:xn,selector:"tb-relations-query-config-old",inputs:["disabled","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:er,decorators:[{type:n,args:[{selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | 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 \n \n
\n
\n \n \n \n
\n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class tr extends s{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.tbelEnabled=W(this.store).tbelEnabled,this.scriptLanguage=d}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:d.JS,[D.required]],jsScript:[e?e.jsScript:null,[D.required]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==d.TBEL||this.tbelEnabled||(t=d.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===d.JS?[D.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===d.TBEL?[D.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=d.JS)),e}testScript(){const e=this.scriptConfigForm.get("scriptLang").value,t=e===d.JS?"jsScript":"tbelScript",n=e===d.JS?"rulenode/transformation_node_script_fn":"rulenode/tbel/transformation_node_script_fn",r=this.scriptConfigForm.get(t).value;this.nodeScriptTestService.testNodeScript(r,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId,n,e).subscribe((e=>{e&&this.scriptConfigForm.get(t).setValue(e)}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===d.JS&&this.jsFuncComponent.validateOnSubmit()}}e("TransformScriptConfigComponent",tr),tr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:tr,deps:[{token:G.Store},{token:E.UntypedFormBuilder},{token:X.NodeScriptTestService},{token:j.TranslateService}],target:t.ɵɵFactoryTarget.Component}),tr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:tr,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',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:re.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:oe.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ae.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:tr,decorators:[{type:n,args:[{selector:"tb-transformation-node-script-config",template:'
\n \n \n \n \n \n
\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder},{type:X.NodeScriptTestService},{type:j.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class nr extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.mailBodyTypes=[{name:"tb.mail-body-type.plain-text",value:"false"},{name:"tb.mail-body-type.html",value:"true"},{name:"tb.mail-body-type.dynamic",value:"dynamic"}]}configForm(){return this.toEmailConfigForm}onConfigurationSet(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[D.required]],toTemplate:[e?e.toTemplate:null,[D.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[D.required]],mailBodyType:[e?e.mailBodyType:null],isHtmlTemplate:[e?e.isHtmlTemplate:null],bodyTemplate:[e?e.bodyTemplate:null,[D.required]]}),this.toEmailConfigForm.get("mailBodyType").valueChanges.pipe(Le([e?.subjectTemplate])).subscribe((e=>{"dynamic"===e?(this.toEmailConfigForm.get("isHtmlTemplate").patchValue("",{emitEvent:!1}),this.toEmailConfigForm.get("isHtmlTemplate").setValidators(D.required)):this.toEmailConfigForm.get("isHtmlTemplate").clearValidators(),this.toEmailConfigForm.get("isHtmlTemplate").updateValueAndValidity()}))}}e("ToEmailConfigComponent",nr),nr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:nr,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),nr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:nr,selector:"tb-transformation-node-to-email-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.mail-body-type\n \n \n {{ type.name | translate }}\n \n \n \n \n tb.rulenode.dynamic-mail-body-type\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | 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:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Q.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:J.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:j.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:nr,decorators:[{type:n,args:[{selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.mail-body-type\n \n \n {{ type.name | translate }}\n \n \n \n \n tb.rulenode.dynamic-mail-body-type\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class rr extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[ie,le,se]}onConfigurationSet(e){this.copyKeysConfigForm=this.fb.group({fromMetadata:[e?e.fromMetadata:null,[D.required]],keys:[e?e.keys:null,[D.required]]})}configForm(){return this.copyKeysConfigForm}removeKey(e){const t=this.copyKeysConfigForm.get("keys").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.copyKeysConfigForm.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.copyKeysConfigForm.get("keys").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.copyKeysConfigForm.get("keys").patchValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("CopyKeysConfigComponent",rr),rr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:rr,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),rr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:rr,selector:"tb-transformation-node-copy-keys-config",usesInheritance:!0,ngImport:t,template:'
\n
{{\'tb.rulenode.copy-from\' | translate}}
\n \n \n {{\'tb.rulenode.data-to-metadata\' | translate}}\n \n \n {{\'tb.rulenode.metadata-to-data\' | translate}}\n \n \n \n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.keys-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:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ze.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{kind:"component",type:ze.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{kind:"component",type:ue.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:ue.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:ue.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:ue.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:_.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:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:rr,decorators:[{type:n,args:[{selector:"tb-transformation-node-copy-keys-config",template:'
\n
{{\'tb.rulenode.copy-from\' | translate}}
\n \n \n {{\'tb.rulenode.data-to-metadata\' | translate}}\n \n \n {{\'tb.rulenode.metadata-to-data\' | translate}}\n \n \n \n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.keys-required\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class or extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.renameKeysConfigForm}onConfigurationSet(e){this.renameKeysConfigForm=this.fb.group({fromMetadata:[e?e.fromMetadata:null,[D.required]],renameKeysMapping:[e?e.renameKeysMapping:null,[D.required]]})}}e("RenameKeysConfigComponent",or),or.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:or,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),or.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:or,selector:"tb-transformation-node-rename-keys-config",usesInheritance:!0,ngImport:t,template:'
\n
{{ \'tb.rulenode.rename-keys-in\' | translate }}
\n \n \n {{\'tb.rulenode.data\' | translate}}\n \n \n {{\'tb.rulenode.metadata\' | translate}}\n \n \n \n \n
\n',dependencies:[{kind:"directive",type:ze.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{kind:"component",type:ze.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{kind:"directive",type:_.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:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Wt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:or,decorators:[{type:n,args:[{selector:"tb-transformation-node-rename-keys-config",template:'
\n
{{ \'tb.rulenode.rename-keys-in\' | translate }}
\n \n \n {{\'tb.rulenode.data\' | translate}}\n \n \n {{\'tb.rulenode.metadata\' | translate}}\n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class ar extends s{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,[D.required]]})}}e("NodeJsonPathConfigComponent",ar),ar.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ar,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ar.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:ar,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\n",dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatLabel,selector:"mat-label"},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.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:E.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ar,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\n"}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class ir extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[ie,le,se]}onConfigurationSet(e){this.deleteKeysConfigForm=this.fb.group({fromMetadata:[e?e.fromMetadata:null,[D.required]],keys:[e?e.keys:null,[D.required]]})}configForm(){return this.deleteKeysConfigForm}removeKey(e){const t=this.deleteKeysConfigForm.get("keys").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.deleteKeysConfigForm.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.deleteKeysConfigForm.get("keys").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.deleteKeysConfigForm.get("keys").patchValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("DeleteKeysConfigComponent",ir),ir.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ir,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ir.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:ir,selector:"tb-transformation-node-delete-keys-config",usesInheritance:!0,ngImport:t,template:'
\n
{{\'tb.rulenode.delete-from\' | translate}}
\n \n \n {{\'tb.rulenode.data\' | translate}}\n \n \n {{\'tb.rulenode.metadata\' | translate}}\n \n \n \n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.keys-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:"component",type:me.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:U.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:z.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:z.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:z.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ze.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{kind:"component",type:ze.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{kind:"component",type:ue.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:ue.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:ue.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:ue.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:_.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:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"},{kind:"pipe",type:Je,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ir,decorators:[{type:n,args:[{selector:"tb-transformation-node-delete-keys-config",template:'
\n
{{\'tb.rulenode.delete-from\' | translate}}
\n \n \n {{\'tb.rulenode.data\' | translate}}\n \n \n {{\'tb.rulenode.metadata\' | translate}}\n \n \n \n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.keys-required\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class lr{}e("RulenodeCoreConfigTransformModule",lr),lr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:lr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),lr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:lr,declarations:[er,tr,nr,rr,or,ar,ir],imports:[K,L,hn],exports:[er,tr,nr,rr,or,ar,ir]}),lr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:lr,imports:[K,L,hn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:lr,decorators:[{type:l,args:[{declarations:[er,tr,nr,rr,or,ar,ir],imports:[K,L,hn],exports:[er,tr,nr,rr,or,ar,ir]}]}]});class sr extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.entityType=x}configForm(){return this.ruleChainInputConfigForm}onConfigurationSet(e){this.ruleChainInputConfigForm=this.fb.group({ruleChainId:[e?e.ruleChainId:null,[D.required]]})}}e("RuleChainInputComponent",sr),sr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:sr,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),sr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:sr,selector:"tb-flow-node-rule-chain-input-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',dependencies:[{kind:"component",type:je.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","appearance","required","disabled"],outputs:["entityChanged"]},{kind:"directive",type:_.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:E.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:E.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:sr,decorators:[{type:n,args:[{selector:"tb-flow-node-rule-chain-input-config",template:'
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class mr extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.ruleChainOutputConfigForm}onConfigurationSet(e){this.ruleChainOutputConfigForm=this.fb.group({})}}e("RuleChainOutputComponent",mr),mr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:mr,deps:[{token:G.Store},{token:E.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),mr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:mr,selector:"tb-flow-node-rule-chain-output-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
\n',dependencies:[{kind:"directive",type:_.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:E.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:E.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"pipe",type:j.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:mr,decorators:[{type:n,args:[{selector:"tb-flow-node-rule-chain-output-config",template:'
\n
\n
\n'}]}],ctorParameters:function(){return[{type:G.Store},{type:E.UntypedFormBuilder}]}});class ur{}e("RuleNodeCoreConfigFlowModule",ur),ur.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ur,deps:[],target:t.ɵɵFactoryTarget.NgModule}),ur.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:ur,declarations:[sr,mr],imports:[K,L,hn],exports:[sr,mr]}),ur.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ur,imports:[K,L,hn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ur,decorators:[{type:l,args:[{declarations:[sr,mr],imports:[K,L,hn],exports:[sr,mr]}]}]});class pr{constructor(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"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-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","output-message-type":"Output message type","output-message-type-required":"Output message type is required","output-message-type-max-length":"Output message type should be less than 256","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":"Time value should be in a range from 1 to 2147483647!","start-interval-value-required":"Start interval value is required!","end-interval-value-required":"End interval value is required!",filter:"Filter",switch:"Switch","math-templatization-tooltip":"This field support templatization. Use $[messageKey] to extract value from the message body and ${metadataKey} to extract value from the message metadata.","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","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":"Notify 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.","fetch-credentials-to-metadata":"Fetch credentials to metadata","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","notify-device-delete-hint":"Send notification about deleted attributes to device.","latest-timeseries":"Latest time-series data keys","timeseries-keys":"Timeseries keys","add-timeseries-key":"Add timeseries key","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":"Hint: use regular expression to copy keys by pattern",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","key-name":"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":"Max relation level 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","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*","fields-mapping-required":"At least one field mapping should be specified.","originator-fields-sv-map-hint":"Target key fields support templatization. Use $[messageKey] to extract value from the message body and ${metadataKey} to extract value from the message metadata.","sv-map-hint":"Only target key fields support templatization. Use $[messageKey] to extract value from the message body and ${metadataKey} to extract value from the message metadata.","source-field":"Source field","source-field-required":"Source field is required!","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","originator-entity":"Entity","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","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 Template","from-template-required":"From Template is required","to-template":"To Template","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 Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","dynamic-mail-body-type":"Dynamic mail body type","mail-body-type":"Mail body type","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","trim-double-quotes":"Message without quotes","trim-double-quotes-hint":"If selected, request body message payload will be sent without double quotes, i.e. msg = message body","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":"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":'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":'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 body and ${metadataKey} to extract value from the message 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-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.',"entity-details":"Select entity 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","entity-details-list-empty":"No entity details 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-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-key-name":"Perimeter key name","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:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, 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-cashing":"Use cashing","use-cashing-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":'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 body and ${metadataKey} to extract value from the message 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 body","message-metadata-type":"Message metadata","argument-tile":"Arguments","no-arguments-prompt":"No arguments configured","result-title":"Result","functions-field-input":"Functions","no-option-found":"No option found","argument-type-field-input":"Type","argument-type-field-input-required":"Argument type 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","key-field-input-required":"Key is required.","number-floating-point-field-input":"Number of digits after floating point","number-floating-point-field-input-hint":"Hint: use 0 to convert result to integer","add-to-body-field-input":"Add to message body","add-to-metadata-field-input":"Add to message metadata","custom-expression-field-input":"Mathematical Expression","custom-expression-field-input-required":"Mathematical expression is required","custom-expression-field-input-hint":"Hint: specify a mathematical expression to evaluate. For example, transform Fahrenheit to Celsius using (x - 32) / 1.8)","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-details":"Clear selected details","clear-selected-keys":"Clear selected keys","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 message metadata with timestamp, e.g: "{{latestTsKeyName}}": "{"ts":1574329385897, "value":42}"',"tell-failure":"Tell Failure","tell-failure-tooltip":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"created-time":"Created time",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 message 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 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:",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"},"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 entry","add-entry":"Add entry","unique-key-value-pair-error":"'{{valText}}' must be different from the current '{{keyText}}'"},"mail-body-type":{"plain-text":"Plain Text",html:"HTML",dynamic:"Dynamic"}}},!0)}(e)}}e("RuleNodeCoreConfigModule",pr),pr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:pr,deps:[{token:j.TranslateService}],target:t.ɵɵFactoryTarget.NgModule}),pr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:pr,declarations:[Qe],imports:[K,L],exports:[Cn,Zn,An,zn,lr,ur,Qe]}),pr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:pr,imports:[K,L,Cn,Zn,An,zn,lr,ur]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:pr,decorators:[{type:l,args:[{declarations:[Qe],imports:[K,L],exports:[Cn,Zn,An,zn,lr,ur,Qe]}]}],ctorParameters:function(){return[{type:j.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/platform-browser","@angular/material/select","@angular/material/core","@shared/components/queue/queue-autocomplete.component","@core/public-api","@shared/components/js-func.component","@angular/material/button","@shared/components/script-lang.component","@angular/cdk/keycodes","@angular/material/icon","@angular/material/chips","@shared/components/entity/entity-type-select.component","@shared/components/entity/entity-select.component","@angular/cdk/coercion","@shared/components/tb-error.component","@angular/material/tooltip","@angular/flex-layout/extended","@angular/material/list","@angular/cdk/drag-drop","rxjs/operators","@angular/material/autocomplete","@shared/pipe/highlight.pipe","rxjs","@angular/material/expansion","@home/components/public-api","tslib","@shared/components/help-popup.component","@shared/components/entity/entity-subtype-list.component","@shared/components/relation/relation-type-autocomplete.component","@angular/material/slide-toggle","@home/components/relation/relation-filters.component","@shared/components/file-input.component","@shared/components/button/toggle-password.component","@shared/components/toggle-header.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"],(function(e){"use strict";var t,n,r,o,a,i,l,s,m,u,p,d,c,f,g,y,x,b,h,C,v,F,L,k,T,I,N,S,q,M,A,G,E,D,V,w,P,R,O,H,K,B,U,z,_,j,$,Q,J,Y,W,X,Z,ee,te,ne,re,oe,ae,ie,le,se,me,ue,pe,de,ce,fe,ge,ye,xe,be,he,Ce,ve,Fe,Le,ke,Te,Ie,Ne,Se,qe,Me,Ae,Ge,Ee,De,Ve,we,Pe,Re,Oe,He,Ke,Be,Ue,ze,_e,je,$e,Qe;return{setters:[function(e){t=e,n=e.Component,r=e.Pipe,o=e.EventEmitter,a=e.ViewChild,i=e.forwardRef,l=e.Input,s=e.NgModule},function(e){m=e.RuleNodeConfigurationComponent,u=e.AttributeScope,p=e.telemetryTypeTranslations,d=e.ServiceType,c=e.ScriptLanguage,f=e.AlarmSeverity,g=e.alarmSeverityTranslations,y=e.EntitySearchDirection,x=e.entitySearchDirectionTranslations,b=e.EntityType,h=e.PageComponent,C=e.coerceBoolean,v=e.MessageType,F=e.messageTypeNames,L=e,k=e.SharedModule,T=e.AggregationType,I=e.aggregationTranslations,N=e.entityFields,S=e.NotificationType,q=e.SlackChanelType,M=e.SlackChanelTypesTranslateMap,A=e.alarmStatusTranslations,G=e.AlarmStatus},function(e){E=e},function(e){D=e,V=e.Validators,w=e.NgControl,P=e.NG_VALUE_ACCESSOR,R=e.NG_VALIDATORS,O=e.FormControl,H=e.UntypedFormControl},function(e){K=e,B=e.CommonModule},function(e){U=e},function(e){z=e},function(e){_=e},function(e){j=e},function(e){$=e},function(e){Q=e},function(e){J=e},function(e){Y=e},function(e){W=e},function(e){X=e.getCurrentAuthState,Z=e,ee=e.isDefinedAndNotNull,te=e.deepTrim,ne=e.isObject,re=e.isNotEmptyStr},function(e){oe=e},function(e){ae=e},function(e){ie=e},function(e){le=e.ENTER,se=e.COMMA,me=e.SEMICOLON},function(e){ue=e},function(e){pe=e},function(e){de=e},function(e){ce=e},function(e){fe=e.coerceBooleanProperty},function(e){ge=e},function(e){ye=e},function(e){xe=e},function(e){be=e},function(e){he=e},function(e){Ce=e.tap,ve=e.map,Fe=e.mergeMap,Le=e.takeUntil,ke=e.startWith,Te=e.share},function(e){Ie=e},function(e){Ne=e},function(e){Se=e.of,qe=e.Subject},function(e){Me=e},function(e){Ae=e.HomeComponentsModule},function(e){Ge=e.__decorate},function(e){Ee=e},function(e){De=e},function(e){Ve=e},function(e){we=e},function(e){Pe=e},function(e){Re=e},function(e){Oe=e},function(e){He=e},function(e){Ke=e},function(e){Be=e},function(e){Ue=e},function(e){ze=e},function(e){_e=e},function(e){je=e},function(e){$e=e},function(e){Qe=e}],execute:function(){class Je extends m{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.emptyConfigForm}onConfigurationSet(e){this.emptyConfigForm=this.fb.group({})}}e("EmptyConfigComponent",Je),Je.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Je,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Je.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Je,selector:"tb-node-empty-config",usesInheritance:!0,ngImport:t,template:"
",isInline:!0}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Je,decorators:[{type:n,args:[{selector:"tb-node-empty-config",template:"
"}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class Ye{constructor(e){this.sanitizer=e}transform(e){return this.sanitizer.bypassSecurityTrustHtml(e)}}e("SafeHtmlPipe",Ye),Ye.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Ye,deps:[{token:Q.DomSanitizer}],target:t.ɵɵFactoryTarget.Pipe}),Ye.ɵpipe=t.ɵɵngDeclarePipe({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:Ye,name:"safeHtml"}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Ye,decorators:[{type:r,args:[{name:"safeHtml"}]}],ctorParameters:function(){return[{type:Q.DomSanitizer}]}});class We extends m{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,[V.required,V.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[V.required,V.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("AssignCustomerConfigComponent",We),We.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:We,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),We.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:We,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 \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:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:We,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 \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:E.Store},{type:D.UntypedFormBuilder}]}});class Xe extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=u,this.attributeScopes=Object.keys(u),this.telemetryTypeTranslationsMap=p}configForm(){return this.attributesConfigForm}onConfigurationSet(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[V.required]],notifyDevice:[!e||e.notifyDevice,[]],sendAttributesUpdatedNotification:[!!e&&e.sendAttributesUpdatedNotification,[]]}),this.attributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==u.SHARED_SCOPE&&this.attributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1}),e===u.CLIENT_SCOPE&&this.attributesConfigForm.get("sendAttributesUpdatedNotification").patchValue(!1,{emitEvent:!1})}))}}e("AttributesConfigComponent",Xe),Xe.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Xe,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Xe.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Xe,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 {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n \n {{ \'tb.rulenode.send-attributes-updated-notification\' | translate }}\n \n
tb.rulenode.send-attributes-updated-notification-hint
\n
\n
\n',dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Xe,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 {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n \n {{ \'tb.rulenode.send-attributes-updated-notification\' | translate }}\n \n
tb.rulenode.send-attributes-updated-notification-hint
\n
\n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class Ze extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.serviceType=d.TB_RULE_ENGINE}configForm(){return this.checkPointConfigForm}onConfigurationSet(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[V.required]]})}}e("CheckPointConfigComponent",Ze),Ze.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Ze,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ze.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Ze,selector:"tb-action-node-check-point-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',dependencies:[{kind:"component",type:W.QueueAutocompleteComponent,selector:"tb-queue-autocomplete",inputs:["labelText","requiredText","autocompleteHint","subscriptSizing","required","queueType","disabled"]},{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Ze,decorators:[{type:n,args:[{selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class et extends m{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.tbelEnabled=X(this.store).tbelEnabled,this.scriptLanguage=c,this.changeScript=new o,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:c.JS,[V.required]],alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[]],alarmDetailsBuildTbel:[e?e.alarmDetailsBuildTbel:null,[]],alarmType:[e?e.alarmType:null,[V.required]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.clearAlarmConfigForm.get("scriptLang").value;t!==c.TBEL||this.tbelEnabled||(t=c.JS,this.clearAlarmConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.clearAlarmConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(t===c.JS?[V.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(t===c.TBEL?[V.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=c.JS)),e}testScript(e){const t=this.clearAlarmConfigForm.get("scriptLang").value,n=t===c.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",r=t===c.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===c.JS&&this.jsFuncComponent.validateOnSubmit()}}e("ClearAlarmConfigComponent",et),et.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:et,deps:[{token:E.Store},{token:D.UntypedFormBuilder},{token:Z.NodeScriptTestService},{token:$.TranslateService}],target:t.ɵɵFactoryTarget.Component}),et.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:et,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 tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:oe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:ae.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ie.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:et,decorators:[{type:n,args:[{selector:"tb-action-node-clear-alarm-config",template:'
\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 \n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder},{type:Z.NodeScriptTestService},{type:$.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{static:!1}]}]}});class tt extends m{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.alarmSeverities=Object.keys(f),this.alarmSeverityTranslationMap=g,this.separatorKeysCodes=[le,se,me],this.tbelEnabled=X(this.store).tbelEnabled,this.scriptLanguage=c,this.changeScript=new o,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:c.JS,[V.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([V.required]),this.createAlarmConfigForm.get("severity").setValidators([V.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e});let r=this.createAlarmConfigForm.get("scriptLang").value;r!==c.TBEL||this.tbelEnabled||(r=c.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===c.JS?[V.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(o&&r===c.TBEL?[V.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=c.JS)),e}testScript(e){const t=this.createAlarmConfigForm.get("scriptLang").value,n=t===c.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",r=t===c.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===c.JS&&this.jsFuncComponent.validateOnSubmit()}}}e("CreateAlarmConfigComponent",tt),tt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:tt,deps:[{token:E.Store},{token:D.UntypedFormBuilder},{token:Z.NodeScriptTestService},{token:$.TranslateService}],target:t.ɵɵFactoryTarget.Component}),tt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:tt,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 tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:oe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:ae.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:U.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:pe.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:pe.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:pe.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:pe.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:j.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:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ie.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:tt,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 tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \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:E.Store},{type:D.UntypedFormBuilder},{type:Z.NodeScriptTestService},{type:$.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{static:!1}]}]}});class nt extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(y),this.directionTypeTranslations=x,this.entityType=b}configForm(){return this.createRelationConfigForm}onConfigurationSet(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[V.required]],entityType:[e?e.entityType:null,[V.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[V.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[V.required,V.min(0)]]})}validatorTriggers(){return["entityType"]}updateValidators(e){const t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([V.required,V.pattern(/.*\S.*/)]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==b.DEVICE&&t!==b.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([V.required,V.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",nt),nt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:nt,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),nt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:nt,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 \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:nt,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 \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \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:E.Store},{type:D.UntypedFormBuilder}]}});class rt extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(y),this.directionTypeTranslations=x,this.entityType=b}configForm(){return this.deleteRelationConfigForm}onConfigurationSet(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[V.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[V.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[V.required,V.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([V.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&n?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([V.required,V.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",rt),rt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:rt,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),rt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:rt,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 \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:rt,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 \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \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:E.Store},{type:D.UntypedFormBuilder}]}});class ot extends m{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,V.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,V.required]})}}e("DeviceProfileConfigComponent",ot),ot.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ot,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ot.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:ot,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:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:j.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:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ot,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:E.Store},{type:D.UntypedFormBuilder}]}});class at extends m{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.tbelEnabled=X(this.store).tbelEnabled,this.scriptLanguage=c,this.changeScript=new o,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-generator-function",this.serviceType=d.TB_RULE_ENGINE}configForm(){return this.generatorConfigForm}onConfigurationSet(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[V.required,V.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[V.required,V.min(1)]],originator:[e?e.originator:null,[]],scriptLang:[e?e.scriptLang:c.JS,[V.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]],queueName:[e?e.queueName:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.generatorConfigForm.get("scriptLang").value;t!==c.TBEL||this.tbelEnabled||(t=c.JS,this.generatorConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.generatorConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.generatorConfigForm.get("jsScript").setValidators(t===c.JS?[V.required]:[]),this.generatorConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.generatorConfigForm.get("tbelScript").setValidators(t===c.TBEL?[V.required]:[]),this.generatorConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=c.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===c.JS?"jsScript":"tbelScript",r=t===c.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===c.JS&&this.jsFuncComponent.validateOnSubmit()}}var it;e("GeneratorConfigComponent",at),at.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:at,deps:[{token:E.Store},{token:D.UntypedFormBuilder},{token:Z.NodeScriptTestService},{token:$.TranslateService}],target:t.ɵɵFactoryTarget.Component}),at.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:at,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
\n',dependencies:[{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ce.EntitySelectComponent,selector:"tb-entity-select",inputs:["allowedEntityTypes","useAliasEntityTypes","required","disabled"]},{kind:"component",type:W.QueueAutocompleteComponent,selector:"tb-queue-autocomplete",inputs:["labelText","requiredText","autocompleteHint","subscriptSizing","required","queueType","disabled"]},{kind:"component",type:oe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:ae.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ie.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:at,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
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder},{type:Z.NodeScriptTestService},{type:$.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{static:!1}]}]}}),function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR",e.ENTITY="ENTITY"}(it||(it={}));const lt=new Map([[it.CUSTOMER,"tb.rulenode.originator-customer"],[it.TENANT,"tb.rulenode.originator-tenant"],[it.RELATED,"tb.rulenode.originator-related"],[it.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"],[it.ENTITY,"tb.rulenode.originator-entity"]]);var st;!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(st||(st={}));const mt=new Map([[st.CIRCLE,"tb.rulenode.perimeter-circle"],[st.POLYGON,"tb.rulenode.perimeter-polygon"]]);var ut;!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(ut||(ut={}));const pt=new Map([[ut.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[ut.SECONDS,"tb.rulenode.time-unit-seconds"],[ut.MINUTES,"tb.rulenode.time-unit-minutes"],[ut.HOURS,"tb.rulenode.time-unit-hours"],[ut.DAYS,"tb.rulenode.time-unit-days"]]);var dt;!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(dt||(dt={}));const ct=new Map([[dt.METER,"tb.rulenode.range-unit-meter"],[dt.KILOMETER,"tb.rulenode.range-unit-kilometer"],[dt.FOOT,"tb.rulenode.range-unit-foot"],[dt.MILE,"tb.rulenode.range-unit-mile"],[dt.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);var ft;!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"}(ft||(ft={}));const gt=new Map([[ft.ID,"tb.rulenode.entity-details-id"],[ft.TITLE,"tb.rulenode.entity-details-title"],[ft.COUNTRY,"tb.rulenode.entity-details-country"],[ft.STATE,"tb.rulenode.entity-details-state"],[ft.CITY,"tb.rulenode.entity-details-city"],[ft.ZIP,"tb.rulenode.entity-details-zip"],[ft.ADDRESS,"tb.rulenode.entity-details-address"],[ft.ADDRESS2,"tb.rulenode.entity-details-address2"],[ft.PHONE,"tb.rulenode.entity-details-phone"],[ft.EMAIL,"tb.rulenode.entity-details-email"],[ft.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);var yt;!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(yt||(yt={}));const xt=new Map([[yt.FIRST,"tb.rulenode.first"],[yt.LAST,"tb.rulenode.last"],[yt.ALL,"tb.rulenode.all"]]),bt=new Map([[yt.FIRST,"tb.rulenode.first-mode-hint"],[yt.LAST,"tb.rulenode.last-mode-hint"],[yt.ALL,"tb.rulenode.all-mode-hint"]]);var ht,Ct;!function(e){e.ASC="ASC",e.DESC="DESC"}(ht||(ht={})),function(e){e.ATTRIBUTES="ATTRIBUTES",e.LATEST_TELEMETRY="LATEST_TELEMETRY",e.FIELDS="FIELDS"}(Ct||(Ct={}));const vt=new Map([[Ct.ATTRIBUTES,"tb.rulenode.attributes"],[Ct.LATEST_TELEMETRY,"tb.rulenode.latest-telemetry"],[Ct.FIELDS,"tb.rulenode.fields"]]),Ft=new Map([[Ct.ATTRIBUTES,"tb.rulenode.add-mapped-attribute-to"],[Ct.LATEST_TELEMETRY,"tb.rulenode.add-mapped-latest-telemetry-to"],[Ct.FIELDS,"tb.rulenode.add-mapped-fields-to"]]),Lt=new Map([[ht.ASC,"tb.rulenode.ascending"],[ht.DESC,"tb.rulenode.descending"]]);var kt;!function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(kt||(kt={}));const Tt=new Map([[kt.STANDARD,"tb.rulenode.sqs-queue-standard"],[kt.FIFO,"tb.rulenode.sqs-queue-fifo"]]),It=["anonymous","basic","cert.PEM"],Nt=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),St=["sas","cert.PEM"],qt=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);var Mt;!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(Mt||(Mt={}));const At=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Gt=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 Et;!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"}(Et||(Et={}));const Dt=new Map([[Et.CUSTOM,{value:Et.CUSTOM,name:"Custom Function",description:"Use this function to specify complex mathematical expression.",minArgs:1,maxArgs:16}],[Et.ADD,{value:Et.ADD,name:"Addition",description:"x + y",minArgs:2,maxArgs:2}],[Et.SUB,{value:Et.SUB,name:"Subtraction",description:"x - y",minArgs:2,maxArgs:2}],[Et.MULT,{value:Et.MULT,name:"Multiplication",description:"x * y",minArgs:2,maxArgs:2}],[Et.DIV,{value:Et.DIV,name:"Division",description:"x / y",minArgs:2,maxArgs:2}],[Et.SIN,{value:Et.SIN,name:"Sine",description:"Returns the trigonometric sine of an angle in radians.",minArgs:1,maxArgs:1}],[Et.SINH,{value:Et.SINH,name:"Hyperbolic sine",description:"Returns the hyperbolic sine of an argument.",minArgs:1,maxArgs:1}],[Et.COS,{value:Et.COS,name:"Cosine",description:"Returns the trigonometric cosine of an angle in radians.",minArgs:1,maxArgs:1}],[Et.COSH,{value:Et.COSH,name:"Hyperbolic cosine",description:"Returns the hyperbolic cosine of an argument.",minArgs:1,maxArgs:1}],[Et.TAN,{value:Et.TAN,name:"Tangent",description:"Returns the trigonometric tangent of an angle in radians",minArgs:1,maxArgs:1}],[Et.TANH,{value:Et.TANH,name:"Hyperbolic tangent",description:"Returns the hyperbolic tangent of an argument",minArgs:1,maxArgs:1}],[Et.ACOS,{value:Et.ACOS,name:"Arc cosine",description:"Returns the arc cosine of an argument",minArgs:1,maxArgs:1}],[Et.ASIN,{value:Et.ASIN,name:"Arc sine",description:"Returns the arc sine of an argument",minArgs:1,maxArgs:1}],[Et.ATAN,{value:Et.ATAN,name:"Arc tangent",description:"Returns the arc tangent of an argument",minArgs:1,maxArgs:1}],[Et.ATAN2,{value:Et.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}],[Et.EXP,{value:Et.EXP,name:"Exponential",description:"Returns Euler's number e raised to the power of an argument",minArgs:1,maxArgs:1}],[Et.EXPM1,{value:Et.EXPM1,name:"Exponential minus one",description:"Returns Euler's number e raised to the power of an argument minus one",minArgs:1,maxArgs:1}],[Et.SQRT,{value:Et.SQRT,name:"Square",description:"Returns the correctly rounded positive square root of an argument",minArgs:1,maxArgs:1}],[Et.CBRT,{value:Et.CBRT,name:"Cube root",description:"Returns the cube root of an argument",minArgs:1,maxArgs:1}],[Et.GET_EXP,{value:Et.GET_EXP,name:"Get exponent",description:"Returns the unbiased exponent used in the representation of an argument",minArgs:1,maxArgs:1}],[Et.HYPOT,{value:Et.HYPOT,name:"Square root",description:"Returns the square root of the squares of the arguments",minArgs:2,maxArgs:2}],[Et.LOG,{value:Et.LOG,name:"Logarithm",description:"Returns the natural logarithm of an argument",minArgs:1,maxArgs:1}],[Et.LOG10,{value:Et.LOG10,name:"Base 10 logarithm",description:"Returns the base 10 logarithm of an argument",minArgs:1,maxArgs:1}],[Et.LOG1P,{value:Et.LOG1P,name:"Logarithm of the sum",description:"Returns the natural logarithm of the sum of an argument",minArgs:1,maxArgs:1}],[Et.CEIL,{value:Et.CEIL,name:"Ceiling",description:"Returns the smallest (closest to negative infinity) of an argument",minArgs:1,maxArgs:1}],[Et.FLOOR,{value:Et.FLOOR,name:"Floor",description:"Returns the largest (closest to positive infinity) of an argument",minArgs:1,maxArgs:1}],[Et.FLOOR_DIV,{value:Et.FLOOR_DIV,name:"Floor division",description:"Returns the largest (closest to positive infinity) of the arguments",minArgs:2,maxArgs:2}],[Et.FLOOR_MOD,{value:Et.FLOOR_MOD,name:"Floor modulus",description:"Returns the floor modulus of the arguments",minArgs:2,maxArgs:2}],[Et.ABS,{value:Et.ABS,name:"Absolute",description:"Returns the absolute value of an argument",minArgs:1,maxArgs:1}],[Et.MIN,{value:Et.MIN,name:"Min",description:"Returns the smaller of the arguments",minArgs:2,maxArgs:2}],[Et.MAX,{value:Et.MAX,name:"Max",description:"Returns the greater of the arguments",minArgs:2,maxArgs:2}],[Et.POW,{value:Et.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}],[Et.SIGNUM,{value:Et.SIGNUM,name:"Sign of a real number",description:"Returns the signum function of the argument",minArgs:1,maxArgs:1}],[Et.RAD,{value:Et.RAD,name:"Radian",description:"Converts an angle measured in degrees to an approximately equivalent angle measured in radians",minArgs:1,maxArgs:1}],[Et.DEG,{value:Et.DEG,name:"Degrees",description:"Converts an angle measured in radians to an approximately equivalent angle measured in degrees.",minArgs:1,maxArgs:1}]]);var Vt,wt,Pt;!function(e){e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES",e.CONSTANT="CONSTANT",e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA"}(Vt||(Vt={})),function(e){e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES",e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA"}(wt||(wt={})),function(e){e.DATA="DATA",e.METADATA="METADATA"}(Pt||(Pt={}));const Rt=new Map([[Pt.DATA,"tb.rulenode.message"],[Pt.METADATA,"tb.rulenode.metadata"]]),Ot=new Map([[Vt.ATTRIBUTE,"tb.rulenode.attribute-type"],[Vt.TIME_SERIES,"tb.rulenode.time-series-type"],[Vt.CONSTANT,"tb.rulenode.constant-type"],[Vt.MESSAGE_BODY,"tb.rulenode.message-body-type"],[Vt.MESSAGE_METADATA,"tb.rulenode.message-metadata-type"]]),Ht=["x","y","z","a","b","c","d","k","l","m","n","o","p","r","s","t"];var Kt,Bt;!function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE",e.CLIENT_SCOPE="CLIENT_SCOPE"}(Kt||(Kt={})),function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE"}(Bt||(Bt={}));const Ut=new Map([[Kt.SHARED_SCOPE,"tb.rulenode.shared-scope"],[Kt.SERVER_SCOPE,"tb.rulenode.server-scope"],[Kt.CLIENT_SCOPE,"tb.rulenode.client-scope"]]);class zt extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=st,this.perimeterTypes=Object.keys(st),this.perimeterTypeTranslationMap=mt,this.rangeUnits=Object.keys(dt),this.rangeUnitTranslationMap=ct,this.timeUnits=Object.keys(ut),this.timeUnitsTranslationMap=pt}configForm(){return this.geoActionConfigForm}onConfigurationSet(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[V.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[V.required]],perimeterType:[e?e.perimeterType:null,[V.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,[V.required,V.min(1),V.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[V.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[V.required,V.min(1),V.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[V.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([V.required]):this.geoActionConfigForm.get("perimeterKeyName").setValidators([]),t||n!==st.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([V.required,V.min(-90),V.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([V.required,V.min(-180),V.max(180)]),this.geoActionConfigForm.get("range").setValidators([V.required,V.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([V.required])),t||n!==st.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([V.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",zt),zt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:zt,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),zt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:zt,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-message-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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:zt,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-message-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:E.Store},{type:D.UntypedFormBuilder}]}});class _t extends m{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.tbelEnabled=X(this.store).tbelEnabled,this.scriptLanguage=c,this.changeScript=new o,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:c.JS,[V.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.logConfigForm.get("scriptLang").value;t!==c.TBEL||this.tbelEnabled||(t=c.JS,this.logConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.logConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.logConfigForm.get("jsScript").setValidators(t===c.JS?[V.required]:[]),this.logConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.logConfigForm.get("tbelScript").setValidators(t===c.TBEL?[V.required]:[]),this.logConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=c.JS)),e}testScript(e){const t=this.logConfigForm.get("scriptLang").value,n=t===c.JS?"jsScript":"tbelScript",r=t===c.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===c.JS&&this.jsFuncComponent.validateOnSubmit()}}e("LogConfigComponent",_t),_t.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:_t,deps:[{token:E.Store},{token:D.UntypedFormBuilder},{token:Z.NodeScriptTestService},{token:$.TranslateService}],target:t.ɵɵFactoryTarget.Component}),_t.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:_t,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',dependencies:[{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:oe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:ae.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ie.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:_t,decorators:[{type:n,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n \n \n
\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder},{type:Z.NodeScriptTestService},{type:$.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{static:!1}]}]}});class jt extends m{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,[V.required,V.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[V.required]]})}}e("MsgCountConfigComponent",jt),jt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:jt,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),jt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:jt,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:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:jt,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:E.Store},{type:D.UntypedFormBuilder}]}});class $t extends m{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,[V.required,V.min(1),V.max(1e5)]]})}validatorTriggers(){return["useMetadataPeriodInSecondsPatterns"]}updateValidators(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([V.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([V.required,V.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})}}e("MsgDelayConfigComponent",$t),$t.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:$t,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),$t.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:$t,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 \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:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:$t,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 \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:E.Store},{type:D.UntypedFormBuilder}]}});class Qt extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(u),this.telemetryTypeTranslationsMap=p}configForm(){return this.pushToCloudConfigForm}onConfigurationSet(e){this.pushToCloudConfigForm=this.fb.group({scope:[e?e.scope:null,[V.required]]})}}e("PushToCloudConfigComponent",Qt),Qt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Qt,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Qt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Qt,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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Qt,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:E.Store},{type:D.UntypedFormBuilder}]}});class Jt extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(u),this.telemetryTypeTranslationsMap=p}configForm(){return this.pushToEdgeConfigForm}onConfigurationSet(e){this.pushToEdgeConfigForm=this.fb.group({scope:[e?e.scope:null,[V.required]]})}}e("PushToEdgeConfigComponent",Jt),Jt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Jt,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Jt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Jt,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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Jt,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:E.Store},{type:D.UntypedFormBuilder}]}});class Yt extends m{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",Yt),Yt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Yt,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Yt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Yt,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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Yt,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:E.Store},{type:D.UntypedFormBuilder}]}});class Wt extends m{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,[V.required,V.min(0)]]})}}e("RpcRequestConfigComponent",Wt),Wt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Wt,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Wt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Wt,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:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Wt,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:E.Store},{type:D.UntypedFormBuilder}]}});class Xt extends h{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(w),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,[V.required]],value:[e[n],[V.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:["",[V.required]],value:["",[V.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",Xt),Xt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Xt,deps:[{token:E.Store},{token:$.TranslateService},{token:t.Injector},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Xt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Xt,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:P,useExisting:i((()=>Xt)),multi:!0},{provide:R,useExisting:i((()=>Xt)),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 .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"],dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ge.TbErrorComponent,selector:"tb-error",inputs:["noMargin","error"]},{kind:"component",type:ae.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:ae.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ye.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:j.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:j.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:j.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:j.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:xe.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"pipe",type:K.AsyncPipe,name:"async"},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Xt,decorators:[{type:n,args:[{selector:"tb-kv-map-config-old",providers:[{provide:P,useExisting:i((()=>Xt)),multi:!0},{provide:R,useExisting:i((()=>Xt)),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 .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"]}]}],ctorParameters:function(){return[{type:E.Store},{type:$.TranslateService},{type:t.Injector},{type:D.UntypedFormBuilder}]},propDecorators:{disabled:[{type:l}],uniqueKeyValuePairValidator:[{type:l}],requiredText:[{type:l}],keyText:[{type:l}],keyRequiredText:[{type:l}],valText:[{type:l}],valRequiredText:[{type:l}],hintText:[{type:l}],required:[{type:l}]}});class Zt extends m{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,[V.required,V.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[V.required]]})}prepareOutputConfig(e){return e.tableName=e.tableName.trim(),e}}e("SaveToCustomTableConfigComponent",Zt),Zt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Zt,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Zt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Zt,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:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Zt,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:E.Store},{type:D.UntypedFormBuilder}]}});class en extends m{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,[V.required,V.min(0)]],skipLatestPersistence:[!!e&&e.skipLatestPersistence,[]],useServerTs:[!!e&&e.useServerTs,[]]})}}e("TimeseriesConfigComponent",en),en.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:en,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),en.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:en,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:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:en,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:E.Store},{type:D.UntypedFormBuilder}]}});class tn extends m{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,[V.required,V.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[V.required,V.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("UnassignCustomerConfigComponent",tn),tn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:tn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),tn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:tn,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 \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:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:tn,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 \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:E.Store},{type:D.UntypedFormBuilder}]}});class nn extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=u,this.attributeScopes=Object.keys(u),this.telemetryTypeTranslationsMap=p,this.separatorKeysCodes=[le,se,me]}configForm(){return this.deleteAttributesConfigForm}onConfigurationSet(e){this.deleteAttributesConfigForm=this.fb.group({scope:[e?e.scope:null,[V.required]],keys:[e?e.keys:null,[V.required]],sendAttributesDeletedNotification:[!!e&&e.sendAttributesDeletedNotification,[]],notifyDevice:[!!e&&e.notifyDevice,[]]}),this.deleteAttributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==u.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",nn),nn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:nn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),nn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:nn,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 \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n \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-delete-hint
\n
\n
\n',dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:pe.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:pe.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:pe.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:pe.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:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:nn,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 \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n \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-delete-hint
\n
\n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]},propDecorators:{attributeChipList:[{type:a,args:["attributeChipList"]}]}});class rn extends h{get function(){return this.functionValue}set function(e){e&&this.functionValue!==e&&(this.functionValue=e,this.setupArgumentsFormGroup(!0))}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.maxArgs=16,this.minArgs=1,this.displayArgumentName=!1,this.mathFunctionMap=Dt,this.ArgumentType=Vt,this.attributeScopeMap=Ut,this.argumentTypeResultMap=Ot,this.arguments=Object.values(Vt),this.attributeScope=Object.values(Kt),this.propagateChange=null,this.valueChangeSubscription=[]}ngOnInit(){this.ngControl=this.injector.get(w),null!=this.ngControl&&(this.ngControl.valueAccessor=this),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()}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.argumentsFormGroup.get("arguments").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.argumentsFormGroup.get("arguments").removeAt(e),this.updateArgumentNames()}addArgument(e=!0){const t=this.argumentsFormGroup.get("arguments"),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===Et.CUSTOM),this.argumentsFormGroup){for(this.argumentsFormGroup.get("arguments").setValidators([V.minLength(this.minArgs),V.maxLength(this.maxArgs)]),this.argumentsFormGroup.get("arguments").value.length>this.maxArgs&&(this.argumentsFormGroup.get("arguments").controls.length=this.maxArgs);this.argumentsFormGroup.get("arguments").value.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===Vt.ATTRIBUTE?e.get("attributeScope").enable({emitEvent:!1}):e.get("attributeScope").disable({emitEvent:!1}),t&&t!==Vt.CONSTANT?e.get("defaultValue").enable({emitEvent:!1}):e.get("defaultValue").disable({emitEvent:!1})}updateArgumentNames(){this.argumentsFormGroup.get("arguments").controls.forEach(((e,t)=>{e.get("name").setValue(Ht[t])}))}updateModel(){const e=this.argumentsFormGroup.get("arguments").value;e.length&&this.argumentsFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}}e("ArgumentsMapConfigComponent",rn),rn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:rn,deps:[{token:E.Store},{token:$.TranslateService},{token:t.Injector},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),rn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:rn,selector:"tb-arguments-map-config",inputs:{disabled:"disabled",function:"function"},providers:[{provide:P,useExisting:i((()=>rn)),multi:!0},{provide:R,useExisting:i((()=>rn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n\n
\n \n \n
\n \n
\n {{argumentControl.get(\'name\').value}}.\n
\n
\n \n tb.rulenode.argument-type-field-input\n \n \n {{ argumentTypeResultMap.get(argument) | translate }}\n \n \n \n tb.rulenode.argument-type-field-input-required\n \n \n \n tb.rulenode.argument-key-field-input\n \n help\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
\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 tb.rulenode.default-value-field-input\n \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:K.NgClass,selector:"[ngClass]",inputs:["class","ngClass"]},{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ae.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:ae.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ye.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:be.MatList,selector:"mat-list",exportAs:["matList"]},{kind:"component",type:be.MatListItem,selector:"mat-list-item, a[mat-list-item], button[mat-list-item]",inputs:["activated"],exportAs:["matListItem"]},{kind:"directive",type:he.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:he.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:he.CdkDragHandle,selector:"[cdkDragHandle]",inputs:["cdkDragHandleDisabled"]},{kind:"directive",type:j.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:j.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:j.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:j.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:xe.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:rn,decorators:[{type:n,args:[{selector:"tb-arguments-map-config",providers:[{provide:P,useExisting:i((()=>rn)),multi:!0},{provide:R,useExisting:i((()=>rn)),multi:!0}],template:'
\n\n
\n \n \n
\n \n
\n {{argumentControl.get(\'name\').value}}.\n
\n
\n \n tb.rulenode.argument-type-field-input\n \n \n {{ argumentTypeResultMap.get(argument) | translate }}\n \n \n \n tb.rulenode.argument-type-field-input-required\n \n \n \n tb.rulenode.argument-key-field-input\n \n help\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
\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 tb.rulenode.default-value-field-input\n \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:E.Store},{type:$.TranslateService},{type:t.Injector},{type:D.FormBuilder}]},propDecorators:{disabled:[{type:l}],function:[{type:l}]}});class on extends h{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=[...Dt.values()],this.propagateChange=null}ngOnInit(){this.mathFunctionForm=this.fb.group({operation:[""]}),this.filteredOptions=this.mathFunctionForm.get("operation").valueChanges.pipe(Ce((e=>{let t;t="string"==typeof e&&Et[e]?Et[e]:null,this.updateView(t)})),ve((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=Dt.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",on),on.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:on,deps:[{token:E.Store},{token:$.TranslateService},{token:t.Injector},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),on.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:on,selector:"tb-math-function-autocomplete",inputs:{required:"required",disabled:"disabled"},providers:[{provide:P,useExisting:i((()=>on)),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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ae.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Ie.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Ie.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:K.AsyncPipe,name:"async"},{kind:"pipe",type:Ne.HighlightPipe,name:"highlight"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:on,decorators:[{type:n,args:[{selector:"tb-math-function-autocomplete",providers:[{provide:P,useExisting:i((()=>on)),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:E.Store},{type:$.TranslateService},{type:t.Injector},{type:D.UntypedFormBuilder}]},propDecorators:{required:[{type:l}],disabled:[{type:l}],operationInput:[{type:a,args:["operationInput",{static:!0}]}]}});class an extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.MathFunction=Et,this.ArgumentTypeResult=wt,this.argumentTypeResultMap=Ot,this.attributeScopeMap=Ut,this.argumentsResult=Object.values(wt),this.attributeScopeResult=Object.values(Bt)}configForm(){return this.mathFunctionConfigForm}onConfigurationSet(e){this.mathFunctionConfigForm=this.fb.group({operation:[e?e.operation:null,[V.required]],arguments:[e?e.arguments:null,[V.required]],customFunction:[e?e.customFunction:"",[V.required]],result:this.fb.group({type:[e?e.result.type:null,[V.required]],attributeScope:[e?e.result.attributeScope:null,[V.required]],key:[e?e.result.key:"",[V.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===Et.CUSTOM?this.mathFunctionConfigForm.get("customFunction").enable({emitEvent:!1}):this.mathFunctionConfigForm.get("customFunction").disable({emitEvent:!1}),n===wt.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",an),an.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:an,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),an.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:an,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 \n \n
\n
\n tb.rulenode.result-title\n
\n
\n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(argument) | translate }}\n \n \n \n tb.rulenode.type-field-input-required\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 \n {{\'tb.rulenode.add-to-body-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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ye.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:j.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:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:D.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:rn,selector:"tb-arguments-map-config",inputs:["disabled","function"]},{kind:"component",type:on,selector:"tb-math-function-autocomplete",inputs:["required","disabled"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:an,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 \n \n
\n
\n tb.rulenode.result-title\n
\n
\n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(argument) | translate }}\n \n \n \n tb.rulenode.type-field-input-required\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 \n {{\'tb.rulenode.add-to-body-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:E.Store},{type:D.UntypedFormBuilder}]}});class ln{constructor(e,t){this.store=e,this.fb=t,this.subscriptSizing="fixed",this.searchText="",this.dirty=!1,this.messageTypes=["POST_ATTRIBUTES_REQUEST","POST_TELEMETRY_REQUEST"],this.propagateChange=e=>{},this.messageTypeFormGroup=this.fb.group({messageType:[null,[V.required,V.maxLength(255)]]})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnInit(){this.outputMessageTypes=this.messageTypeFormGroup.get("messageType").valueChanges.pipe(Ce((e=>{this.updateView(e)})),ve((e=>e||"")),Fe((e=>this.fetchMessageTypes(e))))}writeValue(e){this.searchText="",this.modelValue=e,this.messageTypeFormGroup.get("messageType").patchValue(e,{emitEvent:!1}),this.dirty=!0}onFocus(){this.dirty&&(this.messageTypeFormGroup.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0}),this.dirty=!1)}updateView(e){this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}displayMessageTypeFn(e){return e||void 0}fetchMessageTypes(e,t=!1){return this.searchText=e,Se(this.messageTypes).pipe(ve((n=>n.filter((n=>t?!!e&&n===e:!e||n.toUpperCase().startsWith(e.toUpperCase()))))))}clear(){this.messageTypeFormGroup.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.messageTypeInput.nativeElement.blur(),this.messageTypeInput.nativeElement.focus()}),0)}}e("OutputMessageTypeAutocompleteComponent",ln),ln.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ln,deps:[{token:E.Store},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ln.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:ln,selector:"tb-output-message-type-autocomplete",inputs:{autocompleteHint:"autocompleteHint",subscriptSizing:"subscriptSizing"},providers:[{provide:P,useExisting:i((()=>ln)),multi:!0}],viewQueries:[{propertyName:"messageTypeInput",first:!0,predicate:["messageTypeInput"],descendants:!0,static:!0}],ngImport:t,template:'\n \n \n \n \n {{msgType}}\n \n \n {{autocompleteHint | translate}}\n \n {{ \'tb.rulenode.output-message-type-required\' | translate }}\n \n \n {{ \'tb.rulenode.output-message-type-max-length\' | translate }}\n \n\n',dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ae.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Ie.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Ie.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:K.AsyncPipe,name:"async"},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ln,decorators:[{type:n,args:[{selector:"tb-output-message-type-autocomplete",providers:[{provide:P,useExisting:i((()=>ln)),multi:!0}],template:'\n \n \n \n \n {{msgType}}\n \n \n {{autocompleteHint | translate}}\n \n {{ \'tb.rulenode.output-message-type-required\' | translate }}\n \n \n {{ \'tb.rulenode.output-message-type-max-length\' | translate }}\n \n\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.FormBuilder}]},propDecorators:{messageTypeInput:[{type:a,args:["messageTypeInput",{static:!0}]}],autocompleteHint:[{type:l}],subscriptSizing:[{type:l}]}});class sn extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.destroy$=new qe,this.serviceType=d.TB_RULE_ENGINE,this.deduplicationStrategie=yt,this.deduplicationStrategies=Object.keys(this.deduplicationStrategie),this.deduplicationStrategiesTranslations=xt}configForm(){return this.deduplicationConfigForm}onConfigurationSet(e){this.deduplicationConfigForm=this.fb.group({interval:[ee(e?.interval)?e.interval:null,[V.required,V.min(1)]],strategy:[ee(e?.strategy)?e.strategy:null,[V.required]],outMsgType:[ee(e?.outMsgType)?e.outMsgType:null,[V.required]],queueName:[ee(e?.queueName)?e.queueName:null,[V.required]],maxPendingMsgs:[ee(e?.maxPendingMsgs)?e.maxPendingMsgs:null,[V.required,V.min(1),V.max(1e3)]],maxRetries:[ee(e?.maxRetries)?e.maxRetries:null,[V.required,V.min(0),V.max(100)]]}),this.deduplicationConfigForm.get("strategy").valueChanges.pipe(Le(this.destroy$)).subscribe((e=>{this.enableControl(e)}))}updateValidators(e){this.enableControl(this.deduplicationConfigForm.get("strategy").value)}validatorTriggers(){return["strategy"]}enableControl(e){e===this.deduplicationStrategie.ALL?(this.deduplicationConfigForm.get("outMsgType").enable({emitEvent:!1}),this.deduplicationConfigForm.get("queueName").enable({emitEvent:!1})):(this.deduplicationConfigForm.get("outMsgType").disable({emitEvent:!1}),this.deduplicationConfigForm.get("queueName").disable({emitEvent:!1}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}}e("DeduplicationConfigComponent",sn),sn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:sn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),sn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:sn,selector:"tb-action-node-msg-deduplication-config",usesInheritance:!0,ngImport:t,template:"
\n \n {{'tb.rulenode.interval' | translate}}\n \n {{'tb.rulenode.interval-hint' | translate}}\n \n {{'tb.rulenode.interval-required' | translate}}\n \n \n {{'tb.rulenode.interval-min-error' | translate}}\n \n \n \n {{'tb.rulenode.strategy' | translate}}\n \n \n {{ deduplicationStrategiesTranslations.get(strategy) | translate }}\n \n \n \n {{'tb.rulenode.strategy-first-hint' | translate}}\n {{'tb.rulenode.strategy-last-hint' | translate}}\n \n {{'tb.rulenode.strategy-required' | translate}}\n \n \n
\n \n \n \n \n
\n \n \n \n
\n
Advanced settings
\n
\n
\n
\n \n \n {{'tb.rulenode.max-pending-msgs' | translate}}\n \n {{'tb.rulenode.max-pending-msgs-hint' | translate}}\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 \n \n {{'tb.rulenode.max-retries' | translate}}\n \n {{'tb.rulenode.max-retries-hint' | translate}}\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 \n \n
\n
\n",styles:[":host ::ng-deep .mat-expansion-panel.advanced-settings{border:none;box-shadow:none;padding:0}:host ::ng-deep .mat-expansion-panel.advanced-settings .mat-expansion-panel-body{padding:0}:host ::ng-deep .mat-expansion-panel:not(.mat-expanded) .mat-expansion-panel-header:not([aria-disabled=true]):hover{background:white}\n"],dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:W.QueueAutocompleteComponent,selector:"tb-queue-autocomplete",inputs:["labelText","requiredText","autocompleteHint","subscriptSizing","required","queueType","disabled"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Me.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:Me.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:Me.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:Me.MatExpansionPanelContent,selector:"ng-template[matExpansionPanelContent]"},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ln,selector:"tb-output-message-type-autocomplete",inputs:["autocompleteHint","subscriptSizing"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:sn,decorators:[{type:n,args:[{selector:"tb-action-node-msg-deduplication-config",template:"
\n \n {{'tb.rulenode.interval' | translate}}\n \n {{'tb.rulenode.interval-hint' | translate}}\n \n {{'tb.rulenode.interval-required' | translate}}\n \n \n {{'tb.rulenode.interval-min-error' | translate}}\n \n \n \n {{'tb.rulenode.strategy' | translate}}\n \n \n {{ deduplicationStrategiesTranslations.get(strategy) | translate }}\n \n \n \n {{'tb.rulenode.strategy-first-hint' | translate}}\n {{'tb.rulenode.strategy-last-hint' | translate}}\n \n {{'tb.rulenode.strategy-required' | translate}}\n \n \n
\n \n \n \n \n
\n \n \n \n
\n
Advanced settings
\n
\n
\n
\n \n \n {{'tb.rulenode.max-pending-msgs' | translate}}\n \n {{'tb.rulenode.max-pending-msgs-hint' | translate}}\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 \n \n {{'tb.rulenode.max-retries' | translate}}\n \n {{'tb.rulenode.max-retries-hint' | translate}}\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 \n \n
\n
\n",styles:[":host ::ng-deep .mat-expansion-panel.advanced-settings{border:none;box-shadow:none;padding:0}:host ::ng-deep .mat-expansion-panel.advanced-settings .mat-expansion-panel-body{padding:0}:host ::ng-deep .mat-expansion-panel:not(.mat-expanded) .mat-expansion-panel-header:not([aria-disabled=true]):hover{background:white}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class mn extends h{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,this.disabled=!1,this.uniqueKeyValuePairValidator=!1,this.required=!1}ngOnInit(){this.ngControl=this.injector.get(w),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,[V.required,V.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:[e[n],[V.required,V.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]}));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:["",[V.required,V.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:["",[V.required,V.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]}))}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("KvMapConfigComponent",mn),mn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:mn,deps:[{token:E.Store},{token:$.TranslateService},{token:t.Injector},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),mn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:mn,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:P,useExisting:i((()=>mn)),multi:!0},{provide:R,useExisting:i((()=>mn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n \n
\n
\n
\n
\n \n {{ keyText }}\n \n \n {{ keyRequiredText }}\n \n \n arrow_forward\n \n {{ valText }}\n \n \n {{ valRequiredText }}\n \n \n
\n \n
\n
\n
\n {{ \'tb.rulenode.kv-map-pattern-hint\' | translate }}\n \n
\n
\n
\n \n \n \n
\n \n
\n
\n',styles:[":host ::ng-deep{width:100%}:host ::ng-deep .tb-kv-map-config{margin-bottom:12px}:host ::ng-deep .tb-kv-map-config .map-label{font-weight:400;font-size:12px;color:#3d3d3d;letter-spacing:.25px}:host ::ng-deep .tb-kv-map-config .body{margin-top:7px;max-height:363px;overflow:auto}:host ::ng-deep .tb-kv-map-config .body .mapping-block{margin-bottom:15px}:host ::ng-deep .tb-kv-map-config .body .mapping-block .inputs-block{border:1px solid #E0E0E0;width:100%;border-radius:6px;padding:22px 22px 0;align-items:center}:host ::ng-deep .tb-kv-map-config .body .mapping-block .inputs-block .arrow-icon{width:24px;height:24px;line-height:24px;font-size:24px;margin:0 2px 22px;color:#9e9e9e}:host ::ng-deep .tb-kv-map-config tb-error{display:block;margin-top:-12px;margin-bottom:8px}\n"],dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Ee.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"]},{kind:"component",type:ge.TbErrorComponent,selector:"tb-error",inputs:["noMargin","error"]},{kind:"component",type:ae.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:ae.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ye.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:j.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:j.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:j.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:j.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:xe.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"pipe",type:K.AsyncPipe,name:"async"},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),Ge([C()],mn.prototype,"disabled",void 0),Ge([C()],mn.prototype,"uniqueKeyValuePairValidator",void 0),Ge([C()],mn.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:mn,decorators:[{type:n,args:[{selector:"tb-kv-map-config",providers:[{provide:P,useExisting:i((()=>mn)),multi:!0},{provide:R,useExisting:i((()=>mn)),multi:!0}],template:'
\n
\n \n
\n
\n
\n
\n \n {{ keyText }}\n \n \n {{ keyRequiredText }}\n \n \n arrow_forward\n \n {{ valText }}\n \n \n {{ valRequiredText }}\n \n \n
\n \n
\n
\n
\n {{ \'tb.rulenode.kv-map-pattern-hint\' | translate }}\n \n
\n
\n
\n \n \n \n
\n \n
\n
\n',styles:[":host ::ng-deep{width:100%}:host ::ng-deep .tb-kv-map-config{margin-bottom:12px}:host ::ng-deep .tb-kv-map-config .map-label{font-weight:400;font-size:12px;color:#3d3d3d;letter-spacing:.25px}:host ::ng-deep .tb-kv-map-config .body{margin-top:7px;max-height:363px;overflow:auto}:host ::ng-deep .tb-kv-map-config .body .mapping-block{margin-bottom:15px}:host ::ng-deep .tb-kv-map-config .body .mapping-block .inputs-block{border:1px solid #E0E0E0;width:100%;border-radius:6px;padding:22px 22px 0;align-items:center}:host ::ng-deep .tb-kv-map-config .body .mapping-block .inputs-block .arrow-icon{width:24px;height:24px;line-height:24px;font-size:24px;margin:0 2px 22px;color:#9e9e9e}:host ::ng-deep .tb-kv-map-config tb-error{display:block;margin-top:-12px;margin-bottom:8px}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:$.TranslateService},{type:t.Injector},{type:D.FormBuilder}]},propDecorators:{disabled:[{type:l}],uniqueKeyValuePairValidator:[{type:l}],labelText:[{type:l}],requiredText:[{type:l}],keyText:[{type:l}],keyRequiredText:[{type:l}],valText:[{type:l}],valRequiredText:[{type:l}],hintText:[{type:l}],popupHelpLink:[{type:l}],required:[{type:l}]}});class un{constructor(e,t){this.store=e,this.fb=t,this.destroy$=new qe}ngOnInit(){this.slideToggleControlGroup=this.fb.group({slideToggleControl:[null,[]]}),this.slideToggleControlGroup.get("slideToggleControl").valueChanges.pipe(Le(this.destroy$)).subscribe((e=>{this.propagateChange(e)}))}writeValue(e){this.slideToggleControlGroup.get("slideToggleControl").patchValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){e?this.slideToggleControlGroup.disable({emitEvent:!1}):this.slideToggleControlGroup.enable({emitEvent:!1})}ngOnDestroy(){this.destroy$.next(null),this.destroy$.complete()}}e("SlideToggleComponent",un),un.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:un,deps:[{token:E.Store},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),un.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:un,selector:"tb-slide-toggle",inputs:{slideToggleName:"slideToggleName",slideToggleTooltip:"slideToggleTooltip"},providers:[{provide:P,useExisting:i((()=>un)),multi:!0}],ngImport:t,template:'
\n \n {{ slideToggleName }}\n \n info\n
\n',styles:[":host ::ng-deep .slide-toggle-container{align-items:center}:host ::ng-deep .slide-toggle-container .slide-toggle{margin-right:8px}:host ::ng-deep .slide-toggle-container .slide-toggle label{padding-left:12px}:host ::ng-deep .slide-toggle-container .tooltip-icon{width:18px;height:18px;line-height:18px;font-size:18px;color:#e0e0e0}:host ::ng-deep .slide-toggle-container .tooltip-icon:hover{color:#9e9e9e}\n"],dependencies:[{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ye.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:we.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:un,decorators:[{type:n,args:[{selector:"tb-slide-toggle",providers:[{provide:P,useExisting:i((()=>un)),multi:!0}],template:'
\n \n {{ slideToggleName }}\n \n info\n
\n',styles:[":host ::ng-deep .slide-toggle-container{align-items:center}:host ::ng-deep .slide-toggle-container .slide-toggle{margin-right:8px}:host ::ng-deep .slide-toggle-container .slide-toggle label{padding-left:12px}:host ::ng-deep .slide-toggle-container .tooltip-icon{width:18px;height:18px;line-height:18px;font-size:18px;color:#e0e0e0}:host ::ng-deep .slide-toggle-container .tooltip-icon:hover{color:#9e9e9e}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:D.FormBuilder}]},propDecorators:{slideToggleName:[{type:l}],slideToggleTooltip:[{type:l}]}});class pn extends h{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(y),this.directionTypeTranslations=x,this.entityType=b,this.propagateChange=null}ngOnInit(){this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[V.required]],maxLevel:[null,[V.min(1)]],relationType:[null],deviceTypes:[null,[V.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",pn),pn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:pn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),pn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:pn,selector:"tb-device-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:P,useExisting:i((()=>pn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\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 {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n \n \n \n \n \n \n
\n',styles:[":host .relation-level{margin-bottom:16px}:host .last-level-slide-toggle{margin:8px 0 24px}:host .relation-type-autocomplete{margin-bottom:16px}\n"],dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:De.EntitySubTypeListComponent,selector:"tb-entity-subtype-list",inputs:["label","required","disabled","entityType"]},{kind:"component",type:Ve.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["label","floatLabel","required","disabled","subscriptSizing"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:un,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:pn,decorators:[{type:n,args:[{selector:"tb-device-relations-query-config",providers:[{provide:P,useExisting:i((()=>pn)),multi:!0}],template:'
\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 {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n \n \n \n \n \n \n
\n',styles:[":host .relation-level{margin-bottom:16px}:host .last-level-slide-toggle{margin:8px 0 24px}:host .relation-type-autocomplete{margin-bottom:16px}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]},propDecorators:{disabled:[{type:l}],required:[{type:l}]}});class dn{constructor(){this.required=!1}}e("FieldsetComponent",dn),dn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:dn,deps:[],target:t.ɵɵFactoryTarget.Component}),dn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:dn,selector:"tb-fieldset-component",inputs:{label:"label",required:"required"},ngImport:t,template:'
\n {{ label }}{{ required ? \'*\' : \'\' }}\n
\n \n
\n
\n',styles:[".fields-group{padding:0 16px;margin:0;border:1px solid #E0E0E0;border-radius:4px}.fields-group .fieldset-content{align-items:center}.fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}.fields-group legend{color:#757575;width:-moz-fit-content;width:fit-content;margin-bottom:4px}.fields-group legend+*{display:block}.fields-group legend+*.no-margin-top{margin-top:0}\n"],dependencies:[{kind:"directive",type:j.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"]}]}),Ge([C()],dn.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:dn,decorators:[{type:n,args:[{selector:"tb-fieldset-component",template:'
\n {{ label }}{{ required ? \'*\' : \'\' }}\n
\n \n
\n
\n',styles:[".fields-group{padding:0 16px;margin:0;border:1px solid #E0E0E0;border-radius:4px}.fields-group .fieldset-content{align-items:center}.fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}.fields-group legend{color:#757575;width:-moz-fit-content;width:fit-content;margin-bottom:4px}.fields-group legend+*{display:block}.fields-group legend+*.no-margin-top{margin-top:0}\n"]}]}],propDecorators:{label:[{type:l}],required:[{type:l}]}});class cn extends h{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(y),this.directionTypeTranslations=x,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[V.required]],maxLevel:[null,[V.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",cn),cn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:cn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),cn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:cn,selector:"tb-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:P,useExisting:i((()=>cn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n \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 {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n
\n \n \n \n
\n \n
\n
\n
\n',styles:[":host .map-label{font-weight:400;font-size:12px;color:#3d3d3d;letter-spacing:.25px}:host .last-level-slide-toggle{margin-bottom:18px;display:inline-block}\n"],dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Pe.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]},{kind:"component",type:un,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"component",type:dn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:cn,decorators:[{type:n,args:[{selector:"tb-relations-query-config",providers:[{provide:P,useExisting:i((()=>cn)),multi:!0}],template:'
\n \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 {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n
\n \n \n \n
\n \n
\n
\n
\n',styles:[":host .map-label{font-weight:400;font-size:12px;color:#3d3d3d;letter-spacing:.25px}:host .last-level-slide-toggle{margin-bottom:18px;display:inline-block}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]},propDecorators:{disabled:[{type:l}],required:[{type:l}]}});class fn extends h{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.message-type",this.separatorKeysCodes=[le,se,me],this.messageTypes=[],this.messageTypesList=[],this.searchText="",this.propagateChange=e=>{},this.messageTypeConfigForm=this.fb.group({messageType:[null]});for(const e of Object.keys(v))this.messageTypesList.push({name:F.get(v[e]),value:e})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnInit(){this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(ke(""),ve((e=>e||"")),Fe((e=>this.fetchMessageTypes(e))),Te())}ngAfterViewInit(){}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&&null!=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 Se(this.messageTypesList.filter((t=>t.name.toUpperCase().includes(e))))}return Se(this.messageTypesList)}transformMessageType(e){if((e||"").trim()){let t=null;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",fn),fn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:fn,deps:[{token:E.Store},{token:$.TranslateService},{token:L.TruncatePipe},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),fn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:fn,selector:"tb-message-types-config",inputs:{required:"required",label:"label",placeholder:"placeholder",disabled:"disabled"},providers:[{provide:P,useExisting:i((()=>fn)),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 \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Ie.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Ie.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:Ie.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{kind:"component",type:pe.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:pe.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:pe.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:pe.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:K.AsyncPipe,name:"async"},{kind:"pipe",type:Ne.HighlightPipe,name:"highlight"},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:fn,decorators:[{type:n,args:[{selector:"tb-message-types-config",providers:[{provide:P,useExisting:i((()=>fn)),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 \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:$.TranslateService},{type:L.TruncatePipe},{type:D.FormBuilder}]},propDecorators:{required:[{type:l}],label:[{type:l}],placeholder:[{type:l}],disabled:[{type:l}],chipList:[{type:a,args:["chipList",{static:!1}]}],matAutocomplete:[{type:a,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:a,args:["messageTypeInput",{static:!1}]}]}});class gn extends h{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=It,this.credentialsTypeTranslationsMap=Nt,this.propagateChange=e=>{}}ngOnInit(){this.credentialsConfigFormGroup=this.fb.group({type:[null,[V.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){ee(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([V.required]),this.credentialsConfigFormGroup.get("password").setValidators(this.passwordFieldRequired?[V.required]:[]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(V.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",gn),gn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:gn,deps:[{token:E.Store},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),gn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:gn,selector:"tb-credentials-config",inputs:{required:"required",disableCertPemCredentials:"disableCertPemCredentials",passwordFieldRequired:"passwordFieldRequired"},providers:[{provide:P,useExisting:i((()=>gn)),multi:!0},{provide:R,useExisting:i((()=>gn)),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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:K.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{kind:"directive",type:K.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Me.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:Me.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:Me.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:Me.MatExpansionPanelDescription,selector:"mat-panel-description"},{kind:"directive",type:Me.MatExpansionPanelContent,selector:"ng-template[matExpansionPanelContent]"},{kind:"directive",type:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Re.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Oe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:gn,decorators:[{type:n,args:[{selector:"tb-credentials-config",providers:[{provide:P,useExisting:i((()=>gn)),multi:!0},{provide:R,useExisting:i((()=>gn)),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:E.Store},{type:D.FormBuilder}]},propDecorators:{required:[{type:l}],disableCertPemCredentials:[{type:l}],passwordFieldRequired:[{type:l}]}});class yn{constructor(e,t,n){this.store=e,this.fb=t,this.translate=n,this.destroy$=new qe,this.selectOptions=[];for(const e of Rt.keys())this.selectOptions.push({value:e,name:this.translate.instant(Rt.get(e))})}ngOnInit(){this.chipControlGroup=this.fb.group({chipControl:[null,[]]}),this.chipControlGroup.get("chipControl").valueChanges.pipe(Le(this.destroy$)).subscribe((e=>{e&&this.propagateChange(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})}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}}e("MsgMetadataChipComponent",yn),yn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:yn,deps:[{token:E.Store},{token:D.FormBuilder},{token:$.TranslateService}],target:t.ɵɵFactoryTarget.Component}),yn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:yn,selector:"tb-msg-metadata-chip",inputs:{labelText:"labelText"},providers:[{provide:P,useExisting:i((()=>yn)),multi:!0}],ngImport:t,template:'
\n \n \n {{ option.name }}\n \n
\n',styles:[":host{width:100%}:host .chip-label{font-weight:400;font-size:12px;letter-spacing:.25px;color:#3d3d3d}\n"],dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:pe.MatChipListbox,selector:"mat-chip-listbox",inputs:["tabIndex","multiple","aria-orientation","selectable","compareWith","required","hideSingleSelectionIndicator","value"],outputs:["change"]},{kind:"component",type:pe.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:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:yn,decorators:[{type:n,args:[{selector:"tb-msg-metadata-chip",providers:[{provide:P,useExisting:i((()=>yn)),multi:!0}],template:'
\n \n \n {{ option.name }}\n \n
\n',styles:[":host{width:100%}:host .chip-label{font-weight:400;font-size:12px;letter-spacing:.25px;color:#3d3d3d}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:D.FormBuilder},{type:$.TranslateService}]},propDecorators:{labelText:[{type:l}]}});class xn extends h{constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.destroy$=new qe,this.sourceFieldSubcritption=[],this.propagateChange=null,this.valueChangeSubscription=null,this.disabled=!1,this.required=!1}ngOnInit(){this.ngControl=this.injector.get(w),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.svListFormGroup=this.fb.group({}),this.svListFormGroup.addControl("keyVals",this.fb.array([]))}keyValsFormArray(){return this.svListFormGroup.get("keyVals")}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}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){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,[V.required]],value:[e[n],[V.required,V.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]}));this.svListFormGroup.setControl("keyVals",this.fb.array(t));for(const e of this.keyValsFormArray().controls)this.keyChangeSubscribe(e);this.valueChangeSubscription=this.svListFormGroup.valueChanges.pipe(Le(this.destroy$)).subscribe((e=>{this.updateModel()}))}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)ee(t.find((e=>e.value===r.value)))&&r.value!==e?.get("key").value||n.push(r);return n}removeKeyVal(e){this.svListFormGroup.get("keyVals").removeAt(e),this.sourceFieldSubcritption[e].unsubscribe(),this.sourceFieldSubcritption.splice(e,1)}addKeyVal(){const e=this.svListFormGroup.get("keyVals");e.push(this.fb.group({key:["",[V.required]],value:["",[V.required,V.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]})),this.keyChangeSubscribe(e.controls[e.length-1])}keyChangeSubscribe(e){this.sourceFieldSubcritption.push(e.get("key").valueChanges.pipe(Le(this.destroy$)).subscribe((t=>{e.get("value").patchValue(this.targetKeyPrefix+t[0].toUpperCase()+t.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",xn),xn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:xn,deps:[{token:E.Store},{token:$.TranslateService},{token:t.Injector},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),xn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:xn,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:P,useExisting:i((()=>xn)),multi:!0},{provide:R,useExisting:i((()=>xn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n \n
\n
\n
\n
\n \n {{ selectText }}\n \n \n {{option.name}}\n \n \n \n {{ selectRequiredText }}\n \n \n arrow_forward\n \n {{ valText }}\n \n \n {{ valRequiredText }}\n \n \n
\n \n
\n
\n
\n {{ hintText }}\n \n
\n
\n \n \n
\n \n
\n',styles:[":host ::ng-deep{width:100%}:host ::ng-deep .tb-sv-map-config{margin-bottom:12px}:host ::ng-deep .tb-sv-map-config .map-label{font-weight:400;font-size:12px;color:#3d3d3d;letter-spacing:.25px}:host ::ng-deep .tb-sv-map-config .body{max-height:363px;overflow:auto;margin-top:7px}:host ::ng-deep .tb-sv-map-config .body .mapping-block{margin-bottom:15px}:host ::ng-deep .tb-sv-map-config .body .mapping-block .inputs-block{border:1px solid #E0E0E0;width:100%;border-radius:6px;padding:22px 22px 0;align-items:center}:host ::ng-deep .tb-sv-map-config .body .mapping-block .inputs-block .arrow-icon{width:24px;height:24px;line-height:24px;font-size:24px;margin:0 2px 22px;color:#9e9e9e}:host ::ng-deep .tb-sv-map-config tb-error{display:block;margin-top:-12px;margin-bottom:8px}\n"],dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Ee.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"]},{kind:"component",type:ge.TbErrorComponent,selector:"tb-error",inputs:["noMargin","error"]},{kind:"component",type:ae.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:ae.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ye.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:j.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:j.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:j.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:j.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:xe.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"pipe",type:K.AsyncPipe,name:"async"},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),Ge([C()],xn.prototype,"disabled",void 0),Ge([C()],xn.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:xn,decorators:[{type:n,args:[{selector:"tb-sv-map-config",providers:[{provide:P,useExisting:i((()=>xn)),multi:!0},{provide:R,useExisting:i((()=>xn)),multi:!0}],template:'
\n
\n \n
\n
\n
\n
\n \n {{ selectText }}\n \n \n {{option.name}}\n \n \n \n {{ selectRequiredText }}\n \n \n arrow_forward\n \n {{ valText }}\n \n \n {{ valRequiredText }}\n \n \n
\n \n
\n
\n
\n {{ hintText }}\n \n
\n
\n \n \n
\n \n
\n',styles:[":host ::ng-deep{width:100%}:host ::ng-deep .tb-sv-map-config{margin-bottom:12px}:host ::ng-deep .tb-sv-map-config .map-label{font-weight:400;font-size:12px;color:#3d3d3d;letter-spacing:.25px}:host ::ng-deep .tb-sv-map-config .body{max-height:363px;overflow:auto;margin-top:7px}:host ::ng-deep .tb-sv-map-config .body .mapping-block{margin-bottom:15px}:host ::ng-deep .tb-sv-map-config .body .mapping-block .inputs-block{border:1px solid #E0E0E0;width:100%;border-radius:6px;padding:22px 22px 0;align-items:center}:host ::ng-deep .tb-sv-map-config .body .mapping-block .inputs-block .arrow-icon{width:24px;height:24px;line-height:24px;font-size:24px;margin:0 2px 22px;color:#9e9e9e}:host ::ng-deep .tb-sv-map-config tb-error{display:block;margin-top:-12px;margin-bottom:8px}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:$.TranslateService},{type:t.Injector},{type:D.FormBuilder}]},propDecorators:{selectOptions:[{type:l}],disabled:[{type:l}],labelText:[{type:l}],requiredText:[{type:l}],targetKeyPrefix:[{type:l}],selectText:[{type:l}],selectRequiredText:[{type:l}],valText:[{type:l}],valRequiredText:[{type:l}],hintText:[{type:l}],popupHelpLink:[{type:l}],required:[{type:l}]}});class bn extends h{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(y),this.directionTypeTranslations=x,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[V.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.9",ngImport:t,type:bn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),bn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:bn,selector:"tb-relations-query-config-old",inputs:{disabled:"disabled",required:"required"},providers:[{provide:P,useExisting:i((()=>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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Pe.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:bn,decorators:[{type:n,args:[{selector:"tb-relations-query-config-old",providers:[{provide:P,useExisting:i((()=>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:E.Store},{type:D.UntypedFormBuilder}]},propDecorators:{disabled:[{type:l}],required:[{type:l}]}});class hn{constructor(e,t,n){this.store=e,this.translate=t,this.fb=n,this.destroy$=new qe,this.separatorKeysCodes=[le,se,me]}ngOnInit(){this.attributeControlGroup=this.fb.group({clientAttributeNames:[null,[]],sharedAttributeNames:[null,[]],serverAttributeNames:[null,[]],latestTsKeyNames:[null,[]],getLatestValueWithTs:[!1,[]]}),this.attributeControlGroup.valueChanges.pipe(Le(this.destroy$)).subscribe((e=>{this.propagateChange(e)}))}writeValue(e){this.attributeControlGroup.patchValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){e?this.attributeControlGroup.disable({emitEvent:!1}):this.attributeControlGroup.enable({emitEvent:!1})}ngOnDestroy(){this.destroy$.next(null),this.destroy$.complete()}removeKey(e,t){const n=this.attributeControlGroup.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.attributeControlGroup.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.attributeControlGroup.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.attributeControlGroup.get(t).setValue(e,{emitEvent:!0}))}n&&(n.value="")}clearChipGrid(e){this.attributeControlGroup.get(e).patchValue([],{emitEvent:!0})}}e("SelectAttributesComponent",hn),hn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:hn,deps:[{token:E.Store},{token:$.TranslateService},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),hn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:hn,selector:"tb-select-attributes",inputs:{popupHelpLink:"popupHelpLink"},providers:[{provide:P,useExisting:i((()=>hn)),multi:!0}],ngImport:t,template:'
\n \n tb.rulenode.client-attributes\n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.shared-attributes\n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.server-attributes\n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.latest-telemetry\n \n \n {{key}}\n close\n \n \n \n \n \n
\n {{ \'tb.rulenode.kv-map-pattern-hint\' | translate }}\n \n
\n \n \n
\n',styles:[":host ::ng-deep .chip-grid{width:100%;margin-bottom:16px}:host ::ng-deep .fetch-slide-toggle{width:100%;margin-bottom:22px;display:block}\n"],dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Ee.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"]},{kind:"component",type:ae.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ye.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:pe.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:pe.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:pe.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:pe.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:un,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:hn,decorators:[{type:n,args:[{selector:"tb-select-attributes",providers:[{provide:P,useExisting:i((()=>hn)),multi:!0}],template:'
\n \n tb.rulenode.client-attributes\n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.shared-attributes\n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.server-attributes\n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.latest-telemetry\n \n \n {{key}}\n close\n \n \n \n \n \n
\n {{ \'tb.rulenode.kv-map-pattern-hint\' | translate }}\n \n
\n \n \n
\n',styles:[":host ::ng-deep .chip-grid{width:100%;margin-bottom:16px}:host ::ng-deep .fetch-slide-toggle{width:100%;margin-bottom:22px;display:block}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:$.TranslateService},{type:D.FormBuilder}]},propDecorators:{popupHelpLink:[{type:l}]}});class Cn{}e("RulenodeCoreConfigCommonModule",Cn),Cn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Cn,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Cn.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:Cn,declarations:[mn,pn,cn,fn,gn,Ye,rn,on,ln,Xt,yn,un,xn,dn,bn,hn],imports:[B,k,Ae],exports:[mn,pn,cn,fn,gn,Ye,rn,on,ln,Xt,yn,un,xn,dn,bn,hn]}),Cn.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Cn,imports:[B,k,Ae]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Cn,decorators:[{type:s,args:[{declarations:[mn,pn,cn,fn,gn,Ye,rn,on,ln,Xt,yn,un,xn,dn,bn,hn],imports:[B,k,Ae],exports:[mn,pn,cn,fn,gn,Ye,rn,on,ln,Xt,yn,un,xn,dn,bn,hn]}]}]});class vn{}e("RuleNodeCoreConfigActionModule",vn),vn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:vn,deps:[],target:t.ɵɵFactoryTarget.NgModule}),vn.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:vn,declarations:[nn,Xe,en,Wt,_t,We,et,tt,nt,$t,rt,at,zt,jt,Yt,Zt,tn,Ze,ot,Jt,Qt,an,sn],imports:[B,k,Ae,Cn],exports:[nn,Xe,en,Wt,_t,We,et,tt,nt,$t,rt,at,zt,jt,Yt,Zt,tn,Ze,ot,Jt,Qt,an,sn]}),vn.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:vn,imports:[B,k,Ae,Cn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:vn,decorators:[{type:s,args:[{declarations:[nn,Xe,en,Wt,_t,We,et,tt,nt,$t,rt,at,zt,jt,Yt,Zt,tn,Ze,ot,Jt,Qt,an,sn],imports:[B,k,Ae,Cn],exports:[nn,Xe,en,Wt,_t,We,et,tt,nt,$t,rt,at,zt,jt,Yt,Zt,tn,Ze,ot,Jt,Qt,an,sn]}]}]});class Fn extends m{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[le,se,me]}configForm(){return this.calculateDeltaConfigForm}onConfigurationSet(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e.inputValueKey,[V.required,V.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],outputValueKey:[e.outputValueKey,[V.required,V.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],useCache:[e.useCache,[]],addPeriodBetweenMsgs:[e.addPeriodBetweenMsgs,[]],periodValueKey:[e.periodValueKey,[]],round:[e.round,[V.min(0),V.max(15)]],tellFailureIfDeltaIsNegative:[e.tellFailureIfDeltaIsNegative,[]]})}prepareInputConfig(e){return{inputValueKey:ee(e?.inputValueKey)?e.inputValueKey:null,outputValueKey:ee(e?.outputValueKey)?e.outputValueKey:null,useCache:!ee(e?.useCache)||e.useCache,addPeriodBetweenMsgs:!!ee(e?.addPeriodBetweenMsgs)&&e.addPeriodBetweenMsgs,periodValueKey:ee(e?.periodValueKey)?e.periodValueKey:null,round:ee(e?.round)?e.round:null,tellFailureIfDeltaIsNegative:!ee(e?.tellFailureIfDeltaIsNegative)||e.tellFailureIfDeltaIsNegative}}prepareOutputConfig(e){return te(e)}updateValidators(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([V.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["addPeriodBetweenMsgs"]}}e("CalculateDeltaConfigComponent",Fn),Fn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Fn,deps:[{token:E.Store},{token:$.TranslateService},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Fn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Fn,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 \n \n \n \n
\n \n {{ 'tb.rulenode.period-value-key' | translate }}\n \n \n {{ 'tb.rulenode.period-value-key-required' | translate }}\n \n \n
\n",styles:[":host ::ng-deep .slide-toggles-block .slide-toggle{margin:12px 0}:host ::ng-deep .period-input{margin-top:20px}\n"],dependencies:[{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:un,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Fn,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 \n \n \n \n
\n \n {{ 'tb.rulenode.period-value-key' | translate }}\n \n \n {{ 'tb.rulenode.period-value-key-required' | translate }}\n \n \n
\n",styles:[":host ::ng-deep .slide-toggles-block .slide-toggle{margin:12px 0}:host ::ng-deep .period-input{margin-top:20px}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:$.TranslateService},{type:D.FormBuilder}]}});class Ln extends m{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=Ct;for(const e of vt.keys())e!==Ct.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(vt.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,te(e)}toggleChange(e){this.customerAttributesConfigForm.get("dataToFetch").patchValue(e,{emitEvent:!0})}prepareInputConfig(e){let t,n;return t=ee(e?.telemetry)?e.telemetry?Ct.LATEST_TELEMETRY:Ct.ATTRIBUTES:ee(e?.dataToFetch)?e.dataToFetch:Ct.ATTRIBUTES,n=ee(e?.attrMapping)?e.attrMapping:ee(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:ee(e?.fetchTo)?e.fetchTo:Pt.METADATA}}selectTranslation(e,t){return this.customerAttributesConfigForm.get("dataToFetch").value===Ct.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.customerAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[V.required]],fetchTo:[e.fetchTo]})}}e("CustomerAttributesConfigComponent",Ln),Ln.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Ln,deps:[{token:E.Store},{token:D.FormBuilder},{token:$.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Ln.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Ln,selector:"tb-enrichment-node-customer-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{margin-bottom:12px;width:420px}\n"],dependencies:[{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:He.ToggleHeaderComponent,selector:"tb-toggle-header",inputs:["value","name","useSelectOnMdLg","ignoreMdLgSize","appearance","disabled"],outputs:["valueChange"]},{kind:"component",type:mn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:yn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"component",type:dn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Ln,decorators:[{type:n,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n \n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{margin-bottom:12px;width:420px}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:D.FormBuilder},{type:$.TranslateService}]}});class kn extends m{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,[V.required]],tellFailureIfAbsent:[e.tellFailureIfAbsent,[]],fetchTo:[e.fetchTo,[]],attributesControl:[e.attributesControl,[]]})}prepareInputConfig(e){return ne(e)&&(e.attributesControl={clientAttributeNames:ee(e?.clientAttributeNames)?e.clientAttributeNames:null,latestTsKeyNames:ee(e?.latestTsKeyNames)?e.latestTsKeyNames:null,serverAttributeNames:ee(e?.serverAttributeNames)?e.serverAttributeNames:null,sharedAttributeNames:ee(e?.sharedAttributeNames)?e.sharedAttributeNames:null,getLatestValueWithTs:!!ee(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{deviceRelationsQuery:ee(e?.deviceRelationsQuery)?e.deviceRelationsQuery:null,tellFailureIfAbsent:!ee(e?.tellFailureIfAbsent)||e.tellFailureIfAbsent,fetchTo:ee(e?.fetchTo)?e.fetchTo:Pt.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",kn),kn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:kn,deps:[{token:E.Store},{token:$.TranslateService},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),kn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:kn,selector:"tb-enrichment-node-device-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}:host .device-relations{width:100%}:host .failure-toggle{margin:25px 0}:host .device-attribute{margin-top:12px}\n"],dependencies:[{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:pn,selector:"tb-device-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:yn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"component",type:un,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"component",type:dn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"component",type:hn,selector:"tb-select-attributes",inputs:["popupHelpLink"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:kn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n \n \n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}:host .device-relations{width:100%}:host .failure-toggle{margin:25px 0}:host .device-attribute{margin-top:12px}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:$.TranslateService},{type:D.FormBuilder}]}});class Tn extends m{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.entityDetailsTranslationsMap=gt,this.entityDetailsList=[],this.searchText="",this.displayDetailsFn=this.displayDetails.bind(this);for(const e of Object.keys(ft))this.entityDetailsList.push(ft[e]);this.detailsFormControl=new O(""),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(ke(""),ve((e=>e||"")),Fe((e=>this.fetchEntityDetails(e))),Te())}ngOnInit(){super.ngOnInit()}configForm(){return this.entityDetailsConfigForm}prepareInputConfig(e){let t;return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),this.detailsList=e?e.detailsList:[],t=ee(e?.addToMetadata)?e.addToMetadata?Pt.METADATA:Pt.DATA:e?.fetchTo?e.fetchTo:Pt.DATA,{detailsList:ee(e?.detailsList)?e.detailsList:null,fetchTo:t}}prepareOutputConfig(e){return e.detailsList=this.detailsList,e}onConfigurationSet(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e.detailsList,[V.required]],fetchTo:[e.fetchTo,[]]}),this.detailsList=e?e.detailsList:[]}displayDetails(e){return e?this.translate.instant(gt.get(e)):void 0}fetchEntityDetails(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Se(this.entityDetailsList.filter((t=>this.translate.instant(gt.get(ft[t])).toUpperCase().includes(e))))}return Se(this.entityDetailsList)}detailsFieldSelected(e){this.addDetailsField(e.option.value),this.clear("")}removeDetailsField(e){const t=this.detailsList.indexOf(e);t>=0&&(this.detailsList.splice(t,1),this.entityDetailsConfigForm.get("detailsList").setValue(this.detailsList))}addDetailsField(e){this.detailsList||(this.detailsList=[]);-1===this.detailsList.indexOf(e)&&(this.detailsList.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(this.detailsList))}onEntityDetailsInputFocus(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clearChipGrid(){this.detailsList=[],this.entityDetailsConfigForm.get("detailsList").patchValue([],{emitEvent:!0}),setTimeout((()=>{this.detailsInput.nativeElement.blur(),this.detailsInput.nativeElement.focus()}),0)}clear(e=""){this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.detailsInput.nativeElement.blur(),this.detailsInput.nativeElement.focus()}),0)}}e("EntityDetailsConfigComponent",Tn),Tn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Tn,deps:[{token:E.Store},{token:$.TranslateService},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Tn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Tn,selector:"tb-enrichment-node-entity-details-config",viewQueries:[{propertyName:"detailsInput",first:!0,predicate:["detailsInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.entity-details\' | translate }}\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n \n
\n
\n {{ \'tb.rulenode.no-entity-details-matching\' | translate }}\n
\n
\n
\n
\n {{ \'tb.rulenode.entity-details-list-empty\' | translate }}\n
\n \n
\n',dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ae.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ye.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:Ie.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Ie.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:Ie.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{kind:"component",type:pe.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:pe.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:pe.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:pe.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:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:yn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"pipe",type:K.AsyncPipe,name:"async"},{kind:"pipe",type:Ne.HighlightPipe,name:"highlight"},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Tn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
\n \n {{ \'tb.rulenode.entity-details\' | translate }}\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n \n
\n
\n {{ \'tb.rulenode.no-entity-details-matching\' | translate }}\n
\n
\n
\n
\n {{ \'tb.rulenode.entity-details-list-empty\' | translate }}\n
\n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:$.TranslateService},{type:D.FormBuilder}]},propDecorators:{detailsInput:[{type:a,args:["detailsInput",{static:!1}]}]}});class In extends m{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[le,se,me],this.aggregationTypes=T,this.aggregations=Object.keys(T),this.aggregationTypesTranslations=I,this.fetchMode=yt,this.samplingOrders=Object.keys(ht),this.samplingOrdersTranslate=Lt,this.timeUnits=Object.values(ut),this.timeUnitsTranslationMap=pt,this.deduplicationStrategiesHintTranslations=bt,this.headerOptions=[],this.timeUnitMap={[ut.MILLISECONDS]:1,[ut.SECONDS]:1e3,[ut.MINUTES]:6e4,[ut.HOURS]:36e5,[ut.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 xt.keys())this.headerOptions.push({value:e,name:this.translate.instant(xt.get(e))})}configForm(){return this.getTelemetryFromDatabaseConfigForm}onConfigurationSet(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e.latestTsKeyNames,[]],aggregation:[e.aggregation,[V.required]],fetchMode:[e.fetchMode,[V.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,te(e)}prepareInputConfig(e){return ne(e)&&(e.interval={startInterval:e.startInterval,startIntervalTimeUnit:e.startIntervalTimeUnit,endInterval:e.endInterval,endIntervalTimeUnit:e.endIntervalTimeUnit}),{latestTsKeyNames:ee(e?.latestTsKeyNames)?e.latestTsKeyNames:null,aggregation:ee(e?.aggregation)?e.aggregation:T.NONE,fetchMode:ee(e?.fetchMode)?e.fetchMode:yt.FIRST,orderBy:ee(e?.orderBy)?e.orderBy:ht.ASC,limit:ee(e?.limit)?e.limit:1e3,useMetadataIntervalPatterns:!!ee(e?.useMetadataIntervalPatterns)&&e.useMetadataIntervalPatterns,interval:{startInterval:ee(e?.interval?.startInterval)?e.interval.startInterval:2,startIntervalTimeUnit:ee(e?.interval?.startIntervalTimeUnit)?e.interval.startIntervalTimeUnit:ut.MINUTES,endInterval:ee(e?.interval?.endInterval)?e.interval.endInterval:1,endIntervalTimeUnit:ee(e?.interval?.endIntervalTimeUnit)?e.interval.endIntervalTimeUnit:ut.MINUTES},startIntervalPattern:ee(e?.startIntervalPattern)?e.startIntervalPattern:null,endIntervalPattern:ee(e?.endIntervalPattern)?e.endIntervalPattern:null}}updateValidators(e){const t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,n=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===yt.ALL?(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([V.required]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([V.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([V.required,V.min(2),V.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([V.required,V.pattern(/(?:.|\s)*\S(&:.|\s)*/)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([V.required,V.pattern(/(?:.|\s)*\S(&:.|\s)*/)])):(this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").setValidators([V.required,V.min(1),V.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").setValidators([V.required]),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").setValidators([V.required,V.min(1),V.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").setValidators([V.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="")}}e("GetTelemetryFromDatabaseConfigComponent",In),In.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:In,deps:[{token:E.Store},{token:$.TranslateService},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),In.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:In,selector:"tb-enrichment-node-get-telemetry-from-database",usesInheritance:!0,ngImport:t,template:'
\n \n {{\'tb.rulenode.timeseries-keys\' | translate}}\n \n \n {{key}}\n close\n \n \n \n \n \n {{ "tb.rulenode.general-pattern-hint" | translate }}\n \n \n \n \n\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 {{ \'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 {{ "tb.rulenode.fetch-timeseries-from-to-invalid" | translate }}\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 {{ \'tb.rulenode.metadata-dynamic-interval-hint\' | translate }}\n \n
\n
\n
\n
\n \n
\n \n \n
\n {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get(\'fetchMode\').value) | translate }}\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
\n',styles:[":host ::ng-deep label.tb-title{margin-bottom:-10px}:host ::ng-deep .fetch-interval{margin-top:12px}:host ::ng-deep .fetch-interval .interval-slide-toggle{width:100%;margin:4px 0 16px}:host ::ng-deep .fetch-interval .input-block{width:100%}:host ::ng-deep .interval-description{text-align:center;font-size:12px;color:#3d3d3d;margin-bottom:9px;font-weight:500}:host ::ng-deep .fetch-strategy-fieldset{margin-top:12px}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block{margin-top:8px;align-items:center;width:100%}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block .fetch-mod-toggle{margin-bottom:12px;width:630px}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block .input-block{width:100%}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block .input-block .additional-inputs{margin-bottom:16px}\n"],dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Ee.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"]},{kind:"component",type:ae.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ye.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:pe.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:pe.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:pe.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:pe.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:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:D.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"component",type:He.ToggleHeaderComponent,selector:"tb-toggle-header",inputs:["value","name","useSelectOnMdLg","ignoreMdLgSize","appearance","disabled"],outputs:["valueChange"]},{kind:"component",type:un,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"component",type:dn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:In,decorators:[{type:n,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n {{\'tb.rulenode.timeseries-keys\' | translate}}\n \n \n {{key}}\n close\n \n \n \n \n \n {{ "tb.rulenode.general-pattern-hint" | translate }}\n \n \n \n \n\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 {{ \'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 {{ "tb.rulenode.fetch-timeseries-from-to-invalid" | translate }}\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 {{ \'tb.rulenode.metadata-dynamic-interval-hint\' | translate }}\n \n
\n
\n
\n
\n \n
\n \n \n
\n {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get(\'fetchMode\').value) | translate }}\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
\n',styles:[":host ::ng-deep label.tb-title{margin-bottom:-10px}:host ::ng-deep .fetch-interval{margin-top:12px}:host ::ng-deep .fetch-interval .interval-slide-toggle{width:100%;margin:4px 0 16px}:host ::ng-deep .fetch-interval .input-block{width:100%}:host ::ng-deep .interval-description{text-align:center;font-size:12px;color:#3d3d3d;margin-bottom:9px;font-weight:500}:host ::ng-deep .fetch-strategy-fieldset{margin-top:12px}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block{margin-top:8px;align-items:center;width:100%}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block .fetch-mod-toggle{margin-bottom:12px;width:630px}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block .input-block{width:100%}:host ::ng-deep .fetch-strategy-fieldset .fetch-strategy-block .input-block .additional-inputs{margin-bottom:16px}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:$.TranslateService},{type:D.FormBuilder}]}});class Nn extends m{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 ne(e)&&(e.attributesControl={clientAttributeNames:ee(e?.clientAttributeNames)?e.clientAttributeNames:null,latestTsKeyNames:ee(e?.latestTsKeyNames)?e.latestTsKeyNames:null,serverAttributeNames:ee(e?.serverAttributeNames)?e.serverAttributeNames:null,sharedAttributeNames:ee(e?.sharedAttributeNames)?e.sharedAttributeNames:null,getLatestValueWithTs:!!ee(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{fetchTo:ee(e?.fetchTo)?e.fetchTo:Pt.METADATA,tellFailureIfAbsent:!!ee(e?.tellFailureIfAbsent)&&e.tellFailureIfAbsent,attributesControl:ee(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",Nn),Nn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Nn,deps:[{token:E.Store},{token:$.TranslateService},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Nn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Nn,selector:"tb-enrichment-node-originator-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}:host .failure-slide-toggle{margin:25px 0}\n"],dependencies:[{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:yn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"component",type:un,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"component",type:dn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"component",type:hn,selector:"tb-select-attributes",inputs:["popupHelpLink"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Nn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n \n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}:host .failure-slide-toggle{margin:25px 0}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:$.TranslateService},{type:D.FormBuilder}]}});class Sn extends m{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.originatorFields=[];for(const e of Object.keys(N))this.originatorFields.push({value:N[e].value,name:this.translate.instant(N[e].name)})}configForm(){return this.originatorFieldsConfigForm}prepareOutputConfig(e){return te(e)}prepareInputConfig(e){return{dataMapping:ee(e?.dataMapping)?e.dataMapping:null,ignoreNullStrings:ee(e?.ignoreNullStrings)?e.ignoreNullStrings:null,fetchTo:ee(e?.fetchTo)?e.fetchTo:Pt.METADATA}}onConfigurationSet(e){this.originatorFieldsConfigForm=this.fb.group({dataMapping:[e.dataMapping,[V.required]],ignoreNullStrings:[e.ignoreNullStrings,[]],fetchTo:[e.fetchTo,[]]})}}e("OriginatorFieldsConfigComponent",Sn),Sn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Sn,deps:[{token:E.Store},{token:D.FormBuilder},{token:$.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Sn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Sn,selector:"tb-enrichment-node-originator-fields-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n
\n',styles:[":host .msg-metadata-chip{margin-bottom:12px}:host .skip-slide-toggle{margin-top:20px}\n"],dependencies:[{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:yn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"component",type:un,selector:"tb-slide-toggle",inputs:["slideToggleName","slideToggleTooltip"]},{kind:"component",type:xn,selector:"tb-sv-map-config",inputs:["selectOptions","disabled","labelText","requiredText","targetKeyPrefix","selectText","selectRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:dn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Sn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n \n \n \n \n
\n',styles:[":host .msg-metadata-chip{margin-bottom:12px}:host .skip-slide-toggle{margin-top:20px}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:D.FormBuilder},{type:$.TranslateService}]}});class qn extends m{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.DataToFetch=Ct,this.msgMetadataLabelTranslations=Ft,this.originatorFields=[],this.fetchToData=[],this.destroy$=new qe,this.defaultKvMap={serialNumber:"sn"},this.defaultSvMap={[N.name.value]:`relatedEntity${this.translate.instant(N.name.name)}`},this.dataToFetchPrevValue="";for(const e of Object.keys(N))this.originatorFields.push({value:N[e].value,name:this.translate.instant(N[e].name)});for(const e of vt.keys())this.fetchToData.push({value:e,name:this.translate.instant(vt.get(e))})}toggleChange(e){this.relatedAttributesConfigForm.get("dataToFetch").patchValue(e,{emitEvent:!0})}configForm(){return this.relatedAttributesConfigForm}prepareOutputConfig(e){const t={};for(const n of Object.keys(e.dataMapping))t[n.trim()]=e.dataMapping[n];return e.dataMapping=t,te(e)}prepareInputConfig(e){let t;return ee(e?.telemetry)?this.dataToFetchPrevValue=e.telemetry?Ct.LATEST_TELEMETRY:Ct.ATTRIBUTES:this.dataToFetchPrevValue=ee(e?.dataToFetch)?e.dataToFetch:Ct.ATTRIBUTES,t=ee(e?.attrMapping)?e.attrMapping:ee(e?.dataMapping)?e.dataMapping:null,{relationsQuery:ee(e?.relationsQuery)?e.relationsQuery:null,dataToFetch:this.dataToFetchPrevValue,dataMapping:t,fetchTo:ee(e?.fetchTo)?e.fetchTo:Pt.METADATA}}selectTranslation(e,t){return this.relatedAttributesConfigForm.get("dataToFetch").value===Ct.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e.relationsQuery,[V.required]],dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[V.required]],fetchTo:[e.fetchTo,[]]}),this.relatedAttributesConfigForm.get("dataToFetch").valueChanges.pipe(Le(this.destroy$)).subscribe((e=>{e===Ct.FIELDS&&this.relatedAttributesConfigForm.get("dataMapping").patchValue(this.defaultSvMap,{emitEvent:!1}),e!==Ct.FIELDS&&this.dataToFetchPrevValue===Ct.FIELDS&&this.relatedAttributesConfigForm.get("dataMapping").patchValue(this.defaultKvMap,{emitEvent:!1}),this.dataToFetchPrevValue=e}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}}e("RelatedAttributesConfigComponent",qn),qn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:qn,deps:[{token:E.Store},{token:D.FormBuilder},{token:$.TranslateService}],target:t.ɵɵFactoryTarget.Component}),qn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:qn,selector:"tb-enrichment-node-related-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n',styles:[":host .fetch-data-block{margin-top:12px}:host .fetch-data-block .fetch-to-data-toggle{margin-bottom:12px;width:630px}:host .fetch-data-block .msg-metadata-chip{margin-bottom:12px}\n"],dependencies:[{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:He.ToggleHeaderComponent,selector:"tb-toggle-header",inputs:["value","name","useSelectOnMdLg","ignoreMdLgSize","appearance","disabled"],outputs:["valueChange"]},{kind:"component",type:mn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:cn,selector:"tb-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:yn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"component",type:xn,selector:"tb-sv-map-config",inputs:["selectOptions","disabled","labelText","requiredText","targetKeyPrefix","selectText","selectRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:dn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:qn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n',styles:[":host .fetch-data-block{margin-top:12px}:host .fetch-data-block .fetch-to-data-toggle{margin-bottom:12px;width:630px}:host .fetch-data-block .msg-metadata-chip{margin-bottom:12px}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:D.FormBuilder},{type:$.TranslateService}]}});class Mn extends m{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=Ct;for(const e of vt.keys())e!==Ct.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(vt.get(e))})}configForm(){return this.tenantAttributesConfigForm}toggleChange(e){this.tenantAttributesConfigForm.get("dataToFetch").patchValue(e,{emitEvent:!0})}prepareInputConfig(e){let t,n;return t=ee(e?.telemetry)?e.telemetry?Ct.LATEST_TELEMETRY:Ct.ATTRIBUTES:ee(e?.dataToFetch)?e.dataToFetch:Ct.ATTRIBUTES,n=ee(e?.attrMapping)?e.attrMapping:ee(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:ee(e?.fetchTo)?e.fetchTo:Pt.METADATA}}selectTranslation(e,t){return this.tenantAttributesConfigForm.get("dataToFetch").value===Ct.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.tenantAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[V.required]],fetchTo:[e.fetchTo,[]]})}}e("TenantAttributesConfigComponent",Mn),Mn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Mn,deps:[{token:E.Store},{token:D.FormBuilder},{token:$.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Mn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Mn,selector:"tb-enrichment-node-tenant-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{margin-bottom:12px;width:420px}:host .msg-metadata-chip{margin-bottom:12px}\n"],dependencies:[{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:He.ToggleHeaderComponent,selector:"tb-toggle-header",inputs:["value","name","useSelectOnMdLg","ignoreMdLgSize","appearance","disabled"],outputs:["valueChange"]},{kind:"component",type:mn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:yn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"component",type:dn,selector:"tb-fieldset-component",inputs:["label","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Mn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n \n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{margin-bottom:12px;width:420px}:host .msg-metadata-chip{margin-bottom:12px}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:D.FormBuilder},{type:$.TranslateService}]}});class An extends m{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.fetchDeviceCredentialsConfigForm}prepareInputConfig(e){return{fetchTo:ee(e?.fetchTo)?e.fetchTo:Pt.METADATA}}onConfigurationSet(e){this.fetchDeviceCredentialsConfigForm=this.fb.group({fetchTo:[e.fetchTo,[]]})}}e("FetchDeviceCredentialsConfigComponent",An),An.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:An,deps:[{token:E.Store},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),An.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:An,selector:"./tb-enrichment-node-fetch-device-credentials-config",usesInheritance:!0,ngImport:t,template:'
\n \n
\n',dependencies:[{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:yn,selector:"tb-msg-metadata-chip",inputs:["labelText"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:An,decorators:[{type:n,args:[{selector:"./tb-enrichment-node-fetch-device-credentials-config",template:'
\n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.FormBuilder}]}});class Gn{}e("RulenodeCoreConfigEnrichmentModule",Gn),Gn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Gn,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Gn.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:Gn,declarations:[Ln,Tn,kn,Nn,Sn,In,qn,Mn,Fn,An],imports:[B,k,Cn],exports:[Ln,Tn,kn,Nn,Sn,In,qn,Mn,Fn,An]}),Gn.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Gn,imports:[B,k,Cn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Gn,decorators:[{type:s,args:[{declarations:[Ln,Tn,kn,Nn,Sn,In,qn,Mn,Fn,An],imports:[B,k,Cn],exports:[Ln,Tn,kn,Nn,Sn,In,qn,Mn,Fn,An]}]}]});class En extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.allAzureIotHubCredentialsTypes=St,this.azureIotHubCredentialsTypeTranslationsMap=qt}configForm(){return this.azureIotHubConfigForm}onConfigurationSet(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[V.required]],host:[e?e.host:null,[V.required]],port:[e?e.port:null,[V.required,V.min(1),V.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[V.required,V.min(1),V.max(200)]],clientId:[e?e.clientId:null,[V.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[V.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([V.required]);break;case"cert.PEM":t.get("privateKey").setValidators([V.required]),t.get("privateKeyFileName").setValidators([V.required]),t.get("cert").setValidators([V.required]),t.get("certFileName").setValidators([V.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",En),En.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:En,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),En.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:En,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 \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"],dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:K.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{kind:"directive",type:K.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:Me.MatAccordion,selector:"mat-accordion",inputs:["multi","hideToggle","displayMode","togglePosition"],exportAs:["matAccordion"]},{kind:"component",type:Me.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:Me.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:Me.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:Me.MatExpansionPanelDescription,selector:"mat-panel-description"},{kind:"directive",type:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:D.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"component",type:Re.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Oe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:En,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 \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"]}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class Dn extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.ackValues=["all","-1","0","1"],this.ToByteStandartCharsetTypesValues=At,this.ToByteStandartCharsetTypeTranslationMap=Gt}configForm(){return this.kafkaConfigForm}onConfigurationSet(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[V.required]],keyPattern:[e?e.keyPattern:null],bootstrapServers:[e?e.bootstrapServers:null,[V.required]],retries:[e?e.retries:null,[V.min(0)]],batchSize:[e?e.batchSize:null,[V.min(0)]],linger:[e?e.linger:null,[V.min(0)]],bufferMemory:[e?e.bufferMemory:null,[V.min(0)]],acks:[e?e.acks:null,[V.required]],keySerializer:[e?e.keySerializer:null,[V.required]],valueSerializer:[e?e.valueSerializer:null,[V.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([V.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})}}e("KafkaConfigComponent",Dn),Dn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Dn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Dn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Dn,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 \n \n \n tb.rulenode.key-pattern\n \n \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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Dn,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 \n \n \n tb.rulenode.key-pattern\n \n \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:E.Store},{type:D.UntypedFormBuilder}]}});class Vn extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[]}configForm(){return this.mqttConfigForm}onConfigurationSet(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[V.required]],host:[e?e.host:null,[V.required]],port:[e?e.port:null,[V.required,V.min(1),V.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[V.required,V.min(1),V.max(200)]],clientId:[e?e.clientId:null,[]],appendClientIdSuffix:[{value:!!e&&e.appendClientIdSuffix,disabled:!(e&&re(e.clientId))},[]],cleanSession:[!!e&&e.cleanSession,[]],retainedMessage:[!!e&&e.retainedMessage,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]}),this.subscriptions.push(this.mqttConfigForm.get("clientId").valueChanges.subscribe((e=>{re(e)?this.mqttConfigForm.get("appendClientIdSuffix").enable({emitEvent:!1}):this.mqttConfigForm.get("appendClientIdSuffix").disable({emitEvent:!1})})))}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}}e("MqttConfigComponent",Vn),Vn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Vn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Vn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Vn,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 \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:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:gn,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRequired"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Vn,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 \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:E.Store},{type:D.UntypedFormBuilder}]}});class wn extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.notificationType=S,this.entityType=b}configForm(){return this.notificationConfigForm}onConfigurationSet(e){this.notificationConfigForm=this.fb.group({templateId:[e?e.templateId:null,[V.required]],targets:[e?e.targets:[],[V.required]]})}}e("NotificationConfigComponent",wn),wn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:wn,deps:[{token:E.Store},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),wn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:wn,selector:"tb-external-node-notification-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n
\n',dependencies:[{kind:"component",type:Ke.EntityListComponent,selector:"tb-entity-list",inputs:["entityType","subType","labelText","placeholderText","requiredText","required","disabled","subscriptSizing","hint"]},{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Be.TemplateAutocompleteComponent,selector:"tb-template-autocomplete",inputs:["required","allowCreate","allowEdit","disabled","notificationTypes"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:wn,decorators:[{type:n,args:[{selector:"tb-external-node-notification-config",template:'
\n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.FormBuilder}]}});class Pn extends m{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,[V.required]],topicName:[e?e.topicName:null,[V.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[V.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[V.required]],messageAttributes:[e?e.messageAttributes:null,[]]})}}e("PubSubConfigComponent",Pn),Pn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Pn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Pn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Pn,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:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Re.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Pn,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:E.Store},{type:D.UntypedFormBuilder}]}});class Rn extends m{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,[V.required]],port:[e?e.port:null,[V.required,V.min(1),V.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,[V.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[V.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})}}e("RabbitMqConfigComponent",Rn),Rn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Rn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Rn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Rn,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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Oe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"component",type:Xt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Rn,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:E.Store},{type:D.UntypedFormBuilder}]}});class On extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.proxySchemes=["http","https"],this.httpRequestTypes=Object.keys(Mt)}configForm(){return this.restApiCallConfigForm}onConfigurationSet(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[V.required]],requestMethod:[e?e.requestMethod:null,[V.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],trimDoubleQuotes:[!!e&&e.trimDoubleQuotes,[]],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,[V.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?[V.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(r?[V.required,V.min(1),V.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([V.min(0)])),n?this.restApiCallConfigForm.get("maxQueueSize").setValidators([V.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",On),On.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:On,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),On.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:On,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 \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.trim-double-quotes\' | translate }}\n \n
tb.rulenode.trim-double-quotes-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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:gn,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRequired"]},{kind:"component",type:Xt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:On,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 \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.trim-double-quotes\' | translate }}\n \n
tb.rulenode.trim-double-quotes-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:E.Store},{type:D.UntypedFormBuilder}]}});class Hn extends m{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([V.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([V.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([V.required,V.min(1),V.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([V.required,V.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(n?[V.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(n?[V.required,V.min(1),V.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",Hn),Hn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Hn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Hn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Hn,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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Ue.TbCheckboxComponent,selector:"tb-checkbox",inputs:["disabled","trueValue","falseValue"],outputs:["valueChange"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Oe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Hn,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:E.Store},{type:D.UntypedFormBuilder}]}});class Kn extends m{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,[V.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[V.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([V.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})}}e("SendSmsConfigComponent",Kn),Kn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Kn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Kn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Kn,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 \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ze.SmsProviderConfigurationComponent,selector:"tb-sms-provider-configuration",inputs:["required","disabled"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Kn,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 \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class Bn extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.slackChanelTypes=Object.keys(q),this.slackChanelTypesTranslateMap=M}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,[V.required]],conversationType:[e?e.conversationType:null,[V.required]],conversation:[e?e.conversation:null,[V.required]]})}validatorTriggers(){return["useSystemSettings"]}updateValidators(e){this.slackConfigForm.get("useSystemSettings").value?this.slackConfigForm.get("botToken").clearValidators():this.slackConfigForm.get("botToken").setValidators([V.required]),this.slackConfigForm.get("botToken").updateValueAndValidity({emitEvent:e})}}e("SlackConfigComponent",Bn),Bn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Bn,deps:[{token:E.Store},{token:D.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Bn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Bn,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 \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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_e.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{kind:"component",type:_e.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{kind:"directive",type:j.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:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:je.SlackConversationAutocompleteComponent,selector:"tb-slack-conversation-autocomplete",inputs:["labelText","requiredText","required","disabled","slackChanelType","token"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Bn,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 \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:E.Store},{type:D.FormBuilder}]}});class Un extends m{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,[V.required]],accessKeyId:[e?e.accessKeyId:null,[V.required]],secretAccessKey:[e?e.secretAccessKey:null,[V.required]],region:[e?e.region:null,[V.required]]})}}e("SnsConfigComponent",Un),Un.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Un,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Un.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Un,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 \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:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Un,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 \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:E.Store},{type:D.UntypedFormBuilder}]}});class zn extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.sqsQueueType=kt,this.sqsQueueTypes=Object.keys(kt),this.sqsQueueTypeTranslationsMap=Tt}configForm(){return this.sqsConfigForm}onConfigurationSet(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[V.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[V.required]],delaySeconds:[e?e.delaySeconds:null,[V.min(0),V.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[V.required]],secretAccessKey:[e?e.secretAccessKey:null,[V.required]],region:[e?e.region:null,[V.required]]})}}e("SqsConfigComponent",zn),zn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:zn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),zn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:zn,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 \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:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:zn,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 \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:E.Store},{type:D.UntypedFormBuilder}]}});class _n{}e("RulenodeCoreConfigExternalModule",_n),_n.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:_n,deps:[],target:t.ɵɵFactoryTarget.NgModule}),_n.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:_n,declarations:[Un,zn,Pn,Dn,Vn,wn,Rn,On,Hn,En,Kn,Bn],imports:[B,k,Ae,Cn],exports:[Un,zn,Pn,Dn,Vn,wn,Rn,On,Hn,En,Kn,Bn]}),_n.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:_n,imports:[B,k,Ae,Cn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:_n,decorators:[{type:s,args:[{declarations:[Un,zn,Pn,Dn,Vn,wn,Rn,On,Hn,En,Kn,Bn],imports:[B,k,Ae,Cn],exports:[Un,zn,Pn,Dn,Vn,wn,Rn,On,Hn,En,Kn,Bn]}]}]});class jn extends m{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.alarmStatusTranslationsMap=A,this.alarmStatusList=[],this.searchText="",this.displayStatusFn=this.displayStatus.bind(this);for(const e of Object.keys(G))this.alarmStatusList.push(G[e]);this.statusFormControl=new H(""),this.filteredAlarmStatus=this.statusFormControl.valueChanges.pipe(ke(""),ve((e=>e||"")),Fe((e=>this.fetchAlarmStatus(e))),Te())}ngOnInit(){super.ngOnInit()}configForm(){return this.alarmStatusConfigForm}prepareInputConfig(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e}onConfigurationSet(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[V.required]]})}displayStatus(e){return e?this.translate.instant(A.get(e)):void 0}fetchAlarmStatus(e){const t=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Se(t.filter((t=>this.translate.instant(A.get(G[t])).toUpperCase().includes(e))))}return Se(t)}alarmStatusSelected(e){this.addAlarmStatus(e.option.value),this.clear("")}removeAlarmStatus(e){const t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){const n=t.indexOf(e);n>=0&&(t.splice(n,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}}addAlarmStatus(e){let t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]);-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}getAlarmStatusList(){return this.alarmStatusList.filter((e=>-1===this.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(e)))}onAlarmStatusInputFocus(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.alarmStatusInput.nativeElement.blur(),this.alarmStatusInput.nativeElement.focus()}),0)}}e("CheckAlarmStatusComponent",jn),jn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:jn,deps:[{token:E.Store},{token:$.TranslateService},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),jn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:jn,selector:"tb-filter-node-check-alarm-status-config",viewQueries:[{propertyName:"alarmStatusInput",first:!0,predicate:["alarmStatusInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n',dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ge.TbErrorComponent,selector:"tb-error",inputs:["noMargin","error"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Ie.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Ie.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:Ie.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{kind:"component",type:pe.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:pe.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:pe.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:pe.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:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:K.AsyncPipe,name:"async"},{kind:"pipe",type:Ne.HighlightPipe,name:"highlight"},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:jn,decorators:[{type:n,args:[{selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:$.TranslateService},{type:D.UntypedFormBuilder}]},propDecorators:{alarmStatusInput:[{type:a,args:["alarmStatusInput",{static:!1}]}]}});class $n extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[le,se,me]}configForm(){return this.checkMessageConfigForm}onConfigurationSet(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})}validateConfig(){const e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0}removeMessageName(e){const t=this.checkMessageConfigForm.get("messageNames").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))}removeMetadataName(e){const t=this.checkMessageConfigForm.get("metadataNames").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))}addMessageName(e){const t=e.input;let n=e.value;if((n||"").trim()){n=n.trim();let e=this.checkMessageConfigForm.get("messageNames").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.checkMessageConfigForm.get("messageNames").setValue(e,{emitEvent:!0}))}t&&(t.value="")}addMetadataName(e){const t=e.input;let n=e.value;if((n||"").trim()){n=n.trim();let e=this.checkMessageConfigForm.get("metadataNames").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.checkMessageConfigForm.get("metadataNames").setValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("CheckMessageConfigComponent",$n),$n.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:$n,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),$n.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:$n,selector:"tb-filter-node-check-message-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.data-keys\n \n \n {{messageName}}\n close\n \n \n \n tb.rulenode.separator-hint\n \n \n tb.rulenode.metadata-keys\n \n \n {{metadataName}}\n close\n \n \n \n tb.rulenode.separator-hint\n \n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:U.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"component",type:pe.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:pe.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:pe.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:pe.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:j.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:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:$n,decorators:[{type:n,args:[{selector:"tb-filter-node-check-message-config",template:'
\n \n tb.rulenode.data-keys\n \n \n {{messageName}}\n close\n \n \n \n tb.rulenode.separator-hint\n \n \n tb.rulenode.metadata-keys\n \n \n {{metadataName}}\n close\n \n \n \n tb.rulenode.separator-hint\n \n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"]}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class Qn extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.entitySearchDirection=Object.keys(y),this.entitySearchDirectionTranslationsMap=x}configForm(){return this.checkRelationConfigForm}onConfigurationSet(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[V.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[V.required]:[]],relationType:[e?e.relationType:null,[V.required]]})}validatorTriggers(){return["checkForSingleEntity"]}updateValidators(e){const t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[V.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[V.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})}}e("CheckRelationConfigComponent",Qn),Qn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Qn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Qn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Qn,selector:"tb-filter-node-check-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n',dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:$e.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","useFullEntityId","appearance","required","disabled"],outputs:["entityChanged"]},{kind:"component",type:de.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:Ve.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["label","floatLabel","required","disabled","subscriptSizing"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Qn,decorators:[{type:n,args:[{selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class Jn extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=st,this.perimeterTypes=Object.keys(st),this.perimeterTypeTranslationMap=mt,this.rangeUnits=Object.keys(dt),this.rangeUnitTranslationMap=ct}configForm(){return this.geoFilterConfigForm}onConfigurationSet(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[V.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[V.required]],perimeterType:[e?e.perimeterType:null,[V.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,[]]})}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([V.required]):this.geoFilterConfigForm.get("perimeterKeyName").setValidators([]),t||n!==st.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([V.required,V.min(-90),V.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([V.required,V.min(-180),V.max(180)]),this.geoFilterConfigForm.get("range").setValidators([V.required,V.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([V.required])),t||n!==st.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([V.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",Jn),Jn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Jn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Jn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Jn,selector:"tb-filter-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-message-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 \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n',dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:U.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:D.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Jn,decorators:[{type:n,args:[{selector:"tb-filter-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-message-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 \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class Yn extends m{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.messageTypeConfigForm}onConfigurationSet(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[V.required]]})}}e("MessageTypeConfigComponent",Yn),Yn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Yn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Yn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Yn,selector:"tb-filter-node-message-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n
\n',dependencies:[{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fn,selector:"tb-message-types-config",inputs:["required","label","placeholder","disabled"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Yn,decorators:[{type:n,args:[{selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class Wn extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.allowedEntityTypes=[b.DEVICE,b.ASSET,b.ENTITY_VIEW,b.TENANT,b.CUSTOMER,b.USER,b.DASHBOARD,b.RULE_CHAIN,b.RULE_NODE]}configForm(){return this.originatorTypeConfigForm}onConfigurationSet(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[V.required]]})}}e("OriginatorTypeConfigComponent",Wn),Wn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Wn,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Wn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Wn,selector:"tb-filter-node-originator-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n',dependencies:[{kind:"component",type:Qe.EntityTypeListComponent,selector:"tb-entity-type-list",inputs:["label","floatLabel","required","disabled","subscriptSizing","allowedEntityTypes","ignoreAuthorityFilter"]},{kind:"directive",type:j.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:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Wn,decorators:[{type:n,args:[{selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class Xn extends m{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.tbelEnabled=X(this.store).tbelEnabled,this.scriptLanguage=c,this.changeScript=new o,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-filter-function"}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:c.JS,[V.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==c.TBEL||this.tbelEnabled||(t=c.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===c.JS?[V.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===c.TBEL?[V.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=c.JS)),e}testScript(e){const t=this.scriptConfigForm.get("scriptLang").value,n=t===c.JS?"jsScript":"tbelScript",r=t===c.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===c.JS&&this.jsFuncComponent.validateOnSubmit()}}e("ScriptConfigComponent",Xn),Xn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Xn,deps:[{token:E.Store},{token:D.UntypedFormBuilder},{token:Z.NodeScriptTestService},{token:$.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Xn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Xn,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:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:oe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:ae.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ie.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Xn,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:E.Store},{type:D.UntypedFormBuilder},{type:Z.NodeScriptTestService},{type:$.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{static:!1}]}]}});class Zn extends m{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.tbelEnabled=X(this.store).tbelEnabled,this.scriptLanguage=c,this.changeScript=new o,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-switch-function"}configForm(){return this.switchConfigForm}onConfigurationSet(e){this.switchConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:c.JS,[V.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.switchConfigForm.get("scriptLang").value;t!==c.TBEL||this.tbelEnabled||(t=c.JS,this.switchConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.switchConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.switchConfigForm.get("jsScript").setValidators(t===c.JS?[V.required]:[]),this.switchConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.switchConfigForm.get("tbelScript").setValidators(t===c.TBEL?[V.required]:[]),this.switchConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=c.JS)),e}testScript(e){const t=this.switchConfigForm.get("scriptLang").value,n=t===c.JS?"jsScript":"tbelScript",r=t===c.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===c.JS&&this.jsFuncComponent.validateOnSubmit()}}e("SwitchConfigComponent",Zn),Zn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Zn,deps:[{token:E.Store},{token:D.UntypedFormBuilder},{token:Z.NodeScriptTestService},{token:$.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Zn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:Zn,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:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:oe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:ae.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ie.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:Zn,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:E.Store},{type:D.UntypedFormBuilder},{type:Z.NodeScriptTestService},{type:$.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{static:!1}]}]}});class er{}e("RuleNodeCoreConfigFilterModule",er),er.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:er,deps:[],target:t.ɵɵFactoryTarget.NgModule}),er.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:er,declarations:[$n,Qn,Jn,Yn,Wn,Xn,Zn,jn],imports:[B,k,Cn],exports:[$n,Qn,Jn,Yn,Wn,Xn,Zn,jn]}),er.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:er,imports:[B,k,Cn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:er,decorators:[{type:s,args:[{declarations:[$n,Qn,Jn,Yn,Wn,Xn,Zn,jn],imports:[B,k,Cn],exports:[$n,Qn,Jn,Yn,Wn,Xn,Zn,jn]}]}]});class tr extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.originatorSource=it,this.originatorSources=Object.keys(it),this.originatorSourceTranslationMap=lt,this.allowedEntityTypes=[b.DEVICE,b.ASSET,b.ENTITY_VIEW,b.USER,b.EDGE]}configForm(){return this.changeOriginatorConfigForm}onConfigurationSet(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[V.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===it.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([V.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),t===it.ENTITY?(this.changeOriginatorConfigForm.get("entityType").setValidators([V.required]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([V.required,V.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.9",ngImport:t,type:tr,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),tr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:tr,selector:"tb-transformation-node-change-originator-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | 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 \n \n
\n
\n \n \n \n
\n
\n',dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:j.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:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:bn,selector:"tb-relations-query-config-old",inputs:["disabled","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:tr,decorators:[{type:n,args:[{selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | 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 \n \n
\n
\n \n \n \n
\n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class nr extends m{constructor(e,t,n,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=r,this.tbelEnabled=X(this.store).tbelEnabled,this.scriptLanguage=c,this.changeScript=new o,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:c.JS,[V.required]],jsScript:[e?e.jsScript:null,[V.required]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==c.TBEL||this.tbelEnabled||(t=c.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===c.JS?[V.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===c.TBEL?[V.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=c.JS)),e}testScript(e){const t=this.scriptConfigForm.get("scriptLang").value,n=t===c.JS?"jsScript":"tbelScript",r=t===c.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===c.JS&&this.jsFuncComponent.validateOnSubmit()}}e("TransformScriptConfigComponent",nr),nr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:nr,deps:[{token:E.Store},{token:D.UntypedFormBuilder},{token:Z.NodeScriptTestService},{token:$.TranslateService}],target:t.ɵɵFactoryTarget.Component}),nr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:nr,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',dependencies:[{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:oe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:ae.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ie.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:nr,decorators:[{type:n,args:[{selector:"tb-transformation-node-script-config",template:'
\n \n \n \n \n \n
\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder},{type:Z.NodeScriptTestService},{type:$.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{static:!1}]}]}});class rr extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.mailBodyTypes=[{name:"tb.mail-body-type.plain-text",value:"false"},{name:"tb.mail-body-type.html",value:"true"},{name:"tb.mail-body-type.dynamic",value:"dynamic"}]}configForm(){return this.toEmailConfigForm}onConfigurationSet(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[V.required]],toTemplate:[e?e.toTemplate:null,[V.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[V.required]],mailBodyType:[e?e.mailBodyType:null],isHtmlTemplate:[e?e.isHtmlTemplate:null],bodyTemplate:[e?e.bodyTemplate:null,[V.required]]}),this.toEmailConfigForm.get("mailBodyType").valueChanges.pipe(ke([e?.subjectTemplate])).subscribe((e=>{"dynamic"===e?(this.toEmailConfigForm.get("isHtmlTemplate").patchValue("",{emitEvent:!1}),this.toEmailConfigForm.get("isHtmlTemplate").setValidators(V.required)):this.toEmailConfigForm.get("isHtmlTemplate").clearValidators(),this.toEmailConfigForm.get("isHtmlTemplate").updateValueAndValidity()}))}}e("ToEmailConfigComponent",rr),rr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:rr,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),rr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:rr,selector:"tb-transformation-node-to-email-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.mail-body-type\n \n \n {{ type.name | translate }}\n \n \n \n \n tb.rulenode.dynamic-mail-body-type\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:J.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:Y.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:$.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:rr,decorators:[{type:n,args:[{selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.mail-body-type\n \n \n {{ type.name | translate }}\n \n \n \n \n tb.rulenode.dynamic-mail-body-type\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class or extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[le,se,me]}onConfigurationSet(e){this.copyKeysConfigForm=this.fb.group({fromMetadata:[e?e.fromMetadata:null,[V.required]],keys:[e?e.keys:null,[V.required]]})}configForm(){return this.copyKeysConfigForm}removeKey(e){const t=this.copyKeysConfigForm.get("keys").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.copyKeysConfigForm.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.copyKeysConfigForm.get("keys").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.copyKeysConfigForm.get("keys").patchValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("CopyKeysConfigComponent",or),or.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:or,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),or.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:or,selector:"tb-transformation-node-copy-keys-config",usesInheritance:!0,ngImport:t,template:'
\n
{{\'tb.rulenode.copy-from\' | translate}}
\n \n \n {{\'tb.rulenode.data-to-metadata\' | translate}}\n \n \n {{\'tb.rulenode.metadata-to-data\' | translate}}\n \n \n \n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.keys-required\' | translate }}\n \n \n
\n',dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_e.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{kind:"component",type:_e.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{kind:"component",type:pe.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:pe.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:pe.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:pe.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:j.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:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:or,decorators:[{type:n,args:[{selector:"tb-transformation-node-copy-keys-config",template:'
\n
{{\'tb.rulenode.copy-from\' | translate}}
\n \n \n {{\'tb.rulenode.data-to-metadata\' | translate}}\n \n \n {{\'tb.rulenode.metadata-to-data\' | translate}}\n \n \n \n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.keys-required\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class ar extends m{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.renameKeysConfigForm}onConfigurationSet(e){this.renameKeysConfigForm=this.fb.group({fromMetadata:[e?e.fromMetadata:null,[V.required]],renameKeysMapping:[e?e.renameKeysMapping:null,[V.required]]})}}e("RenameKeysConfigComponent",ar),ar.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ar,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ar.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:ar,selector:"tb-transformation-node-rename-keys-config",usesInheritance:!0,ngImport:t,template:'
\n
{{ \'tb.rulenode.rename-keys-in\' | translate }}
\n \n \n {{\'tb.rulenode.data\' | translate}}\n \n \n {{\'tb.rulenode.metadata\' | translate}}\n \n \n \n \n
\n',dependencies:[{kind:"directive",type:_e.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{kind:"component",type:_e.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{kind:"directive",type:j.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:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Xt,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ar,decorators:[{type:n,args:[{selector:"tb-transformation-node-rename-keys-config",template:'
\n
{{ \'tb.rulenode.rename-keys-in\' | translate }}
\n \n \n {{\'tb.rulenode.data\' | translate}}\n \n \n {{\'tb.rulenode.metadata\' | translate}}\n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class ir extends m{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,[V.required]]})}}e("NodeJsonPathConfigComponent",ir),ir.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ir,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ir.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:ir,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\n",dependencies:[{kind:"directive",type:K.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatLabel,selector:"mat-label"},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:j.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:D.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ir,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\n"}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class lr extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[le,se,me]}onConfigurationSet(e){this.deleteKeysConfigForm=this.fb.group({fromMetadata:[e?e.fromMetadata:null,[V.required]],keys:[e?e.keys:null,[V.required]]})}configForm(){return this.deleteKeysConfigForm}removeKey(e){const t=this.deleteKeysConfigForm.get("keys").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.deleteKeysConfigForm.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.deleteKeysConfigForm.get("keys").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.deleteKeysConfigForm.get("keys").patchValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("DeleteKeysConfigComponent",lr),lr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:lr,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),lr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:lr,selector:"tb-transformation-node-delete-keys-config",usesInheritance:!0,ngImport:t,template:'
\n
{{\'tb.rulenode.delete-from\' | translate}}
\n \n \n {{\'tb.rulenode.data\' | translate}}\n \n \n {{\'tb.rulenode.metadata\' | translate}}\n \n \n \n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.keys-required\' | translate }}\n \n \n
\n',dependencies:[{kind:"directive",type:K.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:K.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:z.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:_.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:_.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:_.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:_e.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{kind:"component",type:_e.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{kind:"component",type:pe.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:pe.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:pe.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:pe.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:j.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:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"},{kind:"pipe",type:Ye,name:"safeHtml"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:lr,decorators:[{type:n,args:[{selector:"tb-transformation-node-delete-keys-config",template:'
\n
{{\'tb.rulenode.delete-from\' | translate}}
\n \n \n {{\'tb.rulenode.data\' | translate}}\n \n \n {{\'tb.rulenode.metadata\' | translate}}\n \n \n \n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.keys-required\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class sr{}e("RulenodeCoreConfigTransformModule",sr),sr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:sr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),sr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:sr,declarations:[tr,nr,rr,or,ar,ir,lr],imports:[B,k,Cn],exports:[tr,nr,rr,or,ar,ir,lr]}),sr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:sr,imports:[B,k,Cn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:sr,decorators:[{type:s,args:[{declarations:[tr,nr,rr,or,ar,ir,lr],imports:[B,k,Cn],exports:[tr,nr,rr,or,ar,ir,lr]}]}]});class mr extends m{constructor(e,t){super(e),this.store=e,this.fb=t,this.entityType=b}configForm(){return this.ruleChainInputConfigForm}onConfigurationSet(e){this.ruleChainInputConfigForm=this.fb.group({ruleChainId:[e?e.ruleChainId:null,[V.required]]})}}e("RuleChainInputComponent",mr),mr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:mr,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),mr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:mr,selector:"tb-flow-node-rule-chain-input-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',dependencies:[{kind:"component",type:$e.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","useFullEntityId","appearance","required","disabled"],outputs:["entityChanged"]},{kind:"directive",type:j.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:D.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:D.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:mr,decorators:[{type:n,args:[{selector:"tb-flow-node-rule-chain-input-config",template:'
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class ur extends m{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.ruleChainOutputConfigForm}onConfigurationSet(e){this.ruleChainOutputConfigForm=this.fb.group({})}}e("RuleChainOutputComponent",ur),ur.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ur,deps:[{token:E.Store},{token:D.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ur.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.9",type:ur,selector:"tb-flow-node-rule-chain-output-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
\n',dependencies:[{kind:"directive",type:j.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:D.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:D.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"pipe",type:$.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:ur,decorators:[{type:n,args:[{selector:"tb-flow-node-rule-chain-output-config",template:'
\n
\n
\n'}]}],ctorParameters:function(){return[{type:E.Store},{type:D.UntypedFormBuilder}]}});class pr{}e("RuleNodeCoreConfigFlowModule",pr),pr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:pr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),pr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:pr,declarations:[mr,ur],imports:[B,k,Cn],exports:[mr,ur]}),pr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:pr,imports:[B,k,Cn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:pr,decorators:[{type:s,args:[{declarations:[mr,ur],imports:[B,k,Cn],exports:[mr,ur]}]}]});class dr{constructor(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"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-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","output-message-type":"Output message type","output-message-type-required":"Output message type is required","output-message-type-max-length":"Output message type should be less than 256","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":"Time value should be in a range from 1 to 2147483647!","start-interval-value-required":"Start interval value is required!","end-interval-value-required":"End interval value is required!",filter:"Filter",switch:"Switch","math-templatization-tooltip":"This field support templatization. Use $[messageKey] to extract value from the message body and ${metadataKey} to extract value from the message metadata.","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","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":"Notify 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.","fetch-credentials-to-metadata":"Fetch credentials to metadata","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","notify-device-delete-hint":"Send notification about deleted attributes to device.","latest-timeseries":"Latest time-series data keys","timeseries-keys":"Timeseries keys","add-timeseries-key":"Add timeseries key","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":"Hint: use regular expression to copy keys by pattern",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","key-name":"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":"Max relation level 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","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*","fields-mapping-required":"At least one field mapping should be specified.","originator-fields-sv-map-hint":"Target key fields support templatization. Use $[messageKey] to extract value from the message body and ${metadataKey} to extract value from the message metadata.","sv-map-hint":"Only target key fields support templatization. Use $[messageKey] to extract value from the message body and ${metadataKey} to extract value from the message metadata.","source-field":"Source field","source-field-required":"Source field is required!","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","originator-entity":"Entity","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","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 Template","from-template-required":"From Template is required","to-template":"To Template","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 Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","dynamic-mail-body-type":"Dynamic mail body type","mail-body-type":"Mail body type","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","trim-double-quotes":"Message without quotes","trim-double-quotes-hint":"If selected, request body message payload will be sent without double quotes, i.e. msg = message body","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":"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":'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":'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 body and ${metadataKey} to extract value from the message 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-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.',"entity-details":"Select entity 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","entity-details-list-empty":"No entity details 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-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-key-name":"Perimeter key name","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:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, 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-cashing":"Use cashing","use-cashing-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":'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 body and ${metadataKey} to extract value from the message 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 body","message-metadata-type":"Message metadata","argument-tile":"Arguments","no-arguments-prompt":"No arguments configured","result-title":"Result","functions-field-input":"Functions","no-option-found":"No option found","argument-type-field-input":"Type","argument-type-field-input-required":"Argument type 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","key-field-input-required":"Key is required.","number-floating-point-field-input":"Number of digits after floating point","number-floating-point-field-input-hint":"Hint: use 0 to convert result to integer","add-to-body-field-input":"Add to message body","add-to-metadata-field-input":"Add to message metadata","custom-expression-field-input":"Mathematical Expression","custom-expression-field-input-required":"Mathematical expression is required","custom-expression-field-input-hint":"Hint: specify a mathematical expression to evaluate. For example, transform Fahrenheit to Celsius using (x - 32) / 1.8)","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-details":"Clear selected details","clear-selected-keys":"Clear selected keys","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 message metadata with timestamp, e.g: "{{latestTsKeyName}}": "{"ts":1574329385897, "value":42}"',"tell-failure":"Tell Failure","tell-failure-tooltip":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"created-time":"Created time",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 message 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 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:",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"},"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 entry","add-entry":"Add entry","unique-key-value-pair-error":"'{{valText}}' must be different from the current '{{keyText}}'"},"mail-body-type":{"plain-text":"Plain Text",html:"HTML",dynamic:"Dynamic"}}},!0)}(e)}}e("RuleNodeCoreConfigModule",dr),dr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:dr,deps:[{token:$.TranslateService}],target:t.ɵɵFactoryTarget.NgModule}),dr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.9",ngImport:t,type:dr,declarations:[Je],imports:[B,k],exports:[vn,er,Gn,_n,sr,pr,Je]}),dr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:dr,imports:[B,k,vn,er,Gn,_n,sr,pr]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.9",ngImport:t,type:dr,decorators:[{type:s,args:[{declarations:[Je],imports:[B,k],exports:[vn,er,Gn,_n,sr,pr,Je]}]}],ctorParameters:function(){return[{type:$.TranslateService}]}})}}}));//# sourceMappingURL=rulenode-core-config.js.map From c3e775c35389ad9ca7bc9bf2c2b8ebf31e41896f Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 18 Jul 2023 11:38:41 +0300 Subject: [PATCH 084/168] added mqtt server chain certificate --- .../src/main/resources/thingsboard.yml | 13 ++--- .../dao/device/DeviceConnectivityInfo.java | 1 + .../DeviceConnectivityMqttSslCertService.java | 53 +++++++++++++++++++ .../server/dao/device/DeviceServiceImpl.java | 8 +++ .../TbDeviceConnectivitySslCertService.java | 21 ++++++++ .../dao/util/DeviceConnectivityUtil.java | 3 +- 6 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/TbDeviceConnectivitySslCertService.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f11cd15bf8..6eb0a3948c 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -990,27 +990,28 @@ device: connectivity: http: enabled: "${DEVICE_CONNECTIVITY_HTTP_ENABLED:true}" - host: "${DEVICE_CONNECTIVITY_HTTP_HOST:localhost}" + host: "${DEVICE_CONNECTIVITY_HTTP_HOST:}" port: "${DEVICE_CONNECTIVITY_HTTP_PORT:8080}" https: enabled: "${DEVICE_CONNECTIVITY_HTTPS_ENABLED:false}" - host: "${DEVICE_CONNECTIVITY_HTTPS_HOST:localhost}" + host: "${DEVICE_CONNECTIVITY_HTTPS_HOST:}" port: "${DEVICE_CONNECTIVITY_HTTPS_PORT:443}" mqtt: enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:true}" - host: "${DEVICE_CONNECTIVITY_MQTT_HOST:localhost}" + host: "${DEVICE_CONNECTIVITY_MQTT_HOST:}" port: "${DEVICE_CONNECTIVITY_MQTT_PORT:1883}" mqtts: enabled: "${DEVICE_CONNECTIVITY_MQTTS_ENABLED:false}" - host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:localhost}" + host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:}" port: "${DEVICE_CONNECTIVITY_MQTTS_PORT:8883}" + tb_server_chain_path: "${DEVICE_CONNECTIVITY_MQTTS_SERVER_CHAIN_PATH:}" coap: enabled: "${DEVICE_CONNECTIVITY_COAP_ENABLED:true}" - host: "${DEVICE_CONNECTIVITY_COAP_HOST:localhost}" + host: "${DEVICE_CONNECTIVITY_COAP_HOST:}" port: "${DEVICE_CONNECTIVITY_COAP_PORT:5683}" coaps: enabled: "${DEVICE_CONNECTIVITY_COAPS_ENABLED:false}" - host: "${DEVICE_CONNECTIVITY_COAPS_HOST:localhost}" + host: "${DEVICE_CONNECTIVITY_COAPS_HOST:}" port: "${DEVICE_CONNECTIVITY_COAPS_PORT:5684}" # Edges parameters diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java index f570919290..5b169a6e79 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java @@ -22,4 +22,5 @@ public class DeviceConnectivityInfo { private Boolean enabled; private String host; private String port; + private String sslCertPath; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java new file mode 100644 index 0000000000..f6736e918f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java @@ -0,0 +1,53 @@ +/** + * 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.dao.device; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ResourceUtils; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; + +@Service +@Slf4j +public class DeviceConnectivityMqttSslCertService implements TbDeviceConnectivitySslCertService { + + private String certificate; + @Autowired + private DeviceConnectivityConfiguration deviceConnectivityConfiguration; + + @PostConstruct + private void postConstruct() throws IOException { + String sslCertPath = deviceConnectivityConfiguration.getConnectivity() + .get(MQTTS) + .getSslCertPath(); + if (!sslCertPath.isEmpty() && ResourceUtils.resourceExists(this, sslCertPath)) { + certificate = FileUtils.readFileToString(new File(sslCertPath), StandardCharsets.UTF_8); + } + } + + @Override + public String getMqttSslCertificate() { + return certificate; + } +} 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 376133b173..f34c1fa99d 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 @@ -99,6 +99,7 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.JSON_EXAMPL import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.CHECK_DOCUMENTATION; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.SERVER_CHAIN_PEM; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCoapClientCommand; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCurlCommand; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getMosquittoPublishCommand; @@ -136,6 +137,9 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Tue, 18 Jul 2023 11:51:18 +0300 Subject: [PATCH 085/168] fixed tests --- .../thingsboard/server/controller/DeviceControllerTest.java | 4 ++-- .../dao/device/DeviceConnectivityMqttSslCertService.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 111ca2e6de..12fa4377f6 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -766,7 +766,7 @@ public class DeviceControllerTest extends AbstractControllerTest { credentials.getCredentialsId())); assertThat(commands.get(COAP)).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -v 9 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } @@ -856,7 +856,7 @@ public class DeviceControllerTest extends AbstractControllerTest { assertThat(commands).hasSize(2); assertThat(commands.get(COAP)).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -v 9 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java index f6736e918f..e5851b43c4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java @@ -41,7 +41,7 @@ public class DeviceConnectivityMqttSslCertService implements TbDeviceConnectivit String sslCertPath = deviceConnectivityConfiguration.getConnectivity() .get(MQTTS) .getSslCertPath(); - if (!sslCertPath.isEmpty() && ResourceUtils.resourceExists(this, sslCertPath)) { + if (sslCertPath != null && ResourceUtils.resourceExists(this, sslCertPath)) { certificate = FileUtils.readFileToString(new File(sslCertPath), StandardCharsets.UTF_8); } } From bebd5021ee35586726eb95a60d1236bf077b8c13 Mon Sep 17 00:00:00 2001 From: rusikv Date: Tue, 18 Jul 2023 12:24:49 +0300 Subject: [PATCH 086/168] Fixed notification rules page not working when rule saved without additional config --- .../home/pages/notification/rule/rule-table-config.resolver.ts | 2 +- ui-ngx/src/app/shared/models/notification.models.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-config.resolver.ts index 86f8120ae0..f547f997bd 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-table-config.resolver.ts @@ -81,7 +81,7 @@ export class RuleTableConfigResolver implements Resolve this.translate.instant(TriggerTypeTranslationMap.get(rule.triggerType)) || '', () => ({}), true), new EntityTableColumn('additionalConfig.description', 'notification.description', '30%', - (target) => target.additionalConfig.description || '', + (target) => target.additionalConfig?.description || '', () => ({}), false) ); } diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts index 4d8a9cffcd..edafef4704 100644 --- a/ui-ngx/src/app/shared/models/notification.models.ts +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -116,7 +116,7 @@ export interface NotificationRule extends Omit, 'la triggerType: TriggerType; triggerConfig: NotificationRuleTriggerConfig; recipientsConfig: NotificationRuleRecipientConfig; - additionalConfig: {description: string}; + additionalConfig?: {description: string}; } export type NotificationRuleTriggerConfig = Partial Date: Wed, 19 Jul 2023 12:28:42 +0300 Subject: [PATCH 087/168] fixes after merge to PE --- .../server/common/data/EntityType.java | 9 ++++-- .../server/common/data/EntityTypeTest.java | 30 +++++++++++++++++++ .../thingsboard/server/common/msg/TbMsg.java | 6 +--- .../TbCopyAttributesToEntityViewNode.java | 4 +-- .../rule/engine/debug/TbMsgGeneratorNode.java | 16 ++++++---- .../engine/telemetry/TbMsgTimeseriesNode.java | 2 +- 6 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 common/data/src/test/java/org/thingsboard/server/common/data/EntityTypeTest.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index dd53d61f8b..014cc2b521 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -35,7 +35,13 @@ public enum EntityType { ALARM, RULE_CHAIN, RULE_NODE, - ENTITY_VIEW, + ENTITY_VIEW { + // backward compatibility for TbMsgTypeSwitchNode to return correct rule node connection. + @Override + public String getNormalName() { + return "Entity View"; + } + }, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, @@ -53,7 +59,6 @@ public enum EntityType { NOTIFICATION, NOTIFICATION_RULE; - public static final List NORMAL_NAMES = EnumSet.allOf(EntityType.class).stream() .map(EntityType::getNormalName).collect(Collectors.toUnmodifiableList()); diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/EntityTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/EntityTypeTest.java new file mode 100644 index 0000000000..9eee9ec23d --- /dev/null +++ b/common/data/src/test/java/org/thingsboard/server/common/data/EntityTypeTest.java @@ -0,0 +1,30 @@ +/** + * 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.common.data; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class EntityTypeTest { + + // backward-compatibility test + @Test + void getNormalNameTest() { + assertThat(EntityType.ENTITY_VIEW.getNormalName()).isEqualTo("Entity View"); + } + +} 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 c6c1a36694..125260def5 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 @@ -438,11 +438,7 @@ public final class TbMsg implements Serializable { public TbMsgCallback getCallback() { // May be null in case of deserialization; - if (callback != null) { - return callback; - } else { - return TbMsgCallback.EMPTY; - } + return Objects.requireNonNullElse(callback, TbMsgCallback.EMPTY); } public void pushToStack(RuleChainId ruleChainId, RuleNodeId ruleNodeId) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 4f69225274..360e81d644 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -96,7 +96,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { if (ATTRIBUTES_DELETED.name().equals(msg.getType())) { List attributes = new ArrayList<>(); - for (JsonElement element : new JsonParser().parse(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray()) { + for (JsonElement element : JsonParser.parseString(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray()) { if (element.isJsonPrimitive()) { JsonPrimitive value = element.getAsJsonPrimitive(); if (value.isString()) { @@ -111,7 +111,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { getFutureCallback(ctx, msg, entityView)); } } else { - Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())); + Set attributes = JsonConverter.convertToAttributes(JsonParser.parseString(msg.getData())); List filteredAttributes = attributes.stream().filter(attr -> attributeContainsInEntityView(scope, attr.getKey(), entityView)).collect(Collectors.toList()); ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), entityView.getId(), scope, filteredAttributes, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 2f1aae5000..febb2c1067 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -28,6 +28,7 @@ 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.StringUtils; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.msg.TbMsgType; @@ -96,7 +97,7 @@ public class TbMsgGeneratorNode implements TbNode { if (initialized.compareAndSet(false, true)) { this.scriptEngine = ctx.createScriptEngine(config.getScriptLang(), ScriptLanguage.TBEL.equals(config.getScriptLang()) ? config.getTbelScript() : config.getJsScript(), "prevMsg", "prevMetadata", "prevMsgType"); - scheduleTickMsg(ctx); + scheduleTickMsg(ctx, null); } } else if (initialized.compareAndSet(true, false)) { destroy(); @@ -113,7 +114,7 @@ public class TbMsgGeneratorNode implements TbNode { log.trace("onMsg onSuccess callback, took {}ms, config {}, msg {}", sw.stopAndGetTotalTimeMillis(), config, msg); if (initialized.get() && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { ctx.enqueueForTellNext(m, TbNodeConnectionType.SUCCESS); - scheduleTickMsg(ctx); + scheduleTickMsg(ctx, msg); currentMsgCount++; } }, @@ -121,14 +122,14 @@ public class TbMsgGeneratorNode implements TbNode { log.trace("onMsg onFailure callback, took {}ms, config {}, msg {}", sw.stopAndGetTotalTimeMillis(), config, msg, t); if (initialized.get() && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { ctx.tellFailure(msg, t); - scheduleTickMsg(ctx); + scheduleTickMsg(ctx, msg); currentMsgCount++; } }); } } - private void scheduleTickMsg(TbContext ctx) { + private void scheduleTickMsg(TbContext ctx, TbMsg msg) { log.trace("scheduleTickMsg, config {}", config); long curTs = System.currentTimeMillis(); if (lastScheduledTs == 0L) { @@ -136,7 +137,8 @@ public class TbMsgGeneratorNode implements TbNode { } lastScheduledTs = lastScheduledTs + delay; long curDelay = Math.max(0L, (lastScheduledTs - curTs)); - TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TbMsgType.GENERATOR_NODE_SELF_MSG, ctx.getSelfId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TbMsgType.GENERATOR_NODE_SELF_MSG, ctx.getSelfId(), + getCustomerIdFromMsg(msg), TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); nextTickId = tickMsg.getId(); ctx.tellSelf(tickMsg, curDelay); } @@ -159,6 +161,10 @@ public class TbMsgGeneratorNode implements TbNode { } + private CustomerId getCustomerIdFromMsg(TbMsg msg) { + return msg != null ? msg.getCustomerId() : null; + } + @Override public void destroy() { log.trace("destroy, config {}", config); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java index 706135eaa2..4118d28c22 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java @@ -88,7 +88,7 @@ public class TbMsgTimeseriesNode implements TbNode { } long ts = computeTs(msg, config.isUseServerTs()); String src = msg.getData(); - Map> tsKvMap = JsonConverter.convertToTelemetry(new JsonParser().parse(src), ts); + Map> tsKvMap = JsonConverter.convertToTelemetry(JsonParser.parseString(src), ts); if (tsKvMap.isEmpty()) { ctx.tellFailure(msg, new IllegalArgumentException("Msg body is empty: " + src)); return; From 2cac9aab9d47841e5e0114a48b57c22812aa4b36 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 19 Jul 2023 12:36:36 +0300 Subject: [PATCH 088/168] fix typo --- .../java/org/thingsboard/server/common/data/EntityType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 014cc2b521..8ca6585718 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -36,7 +36,7 @@ public enum EntityType { RULE_CHAIN, RULE_NODE, ENTITY_VIEW { - // backward compatibility for TbMsgTypeSwitchNode to return correct rule node connection. + // backward compatibility for TbOriginatorTypeSwitchNode to return correct rule node connection. @Override public String getNormalName() { return "Entity View"; From 81f9659af19bb071cd6d5546fddceacdc51a8404 Mon Sep 17 00:00:00 2001 From: rusikv Date: Wed, 19 Jul 2023 14:40:38 +0300 Subject: [PATCH 089/168] Add custom translation for Subject and Text columns in inbox notifications table --- .../notification/inbox/inbox-table-config.resolver.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-config.resolver.ts index f8088c28cb..84aa5d7e33 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/inbox/inbox-table-config.resolver.ts @@ -39,6 +39,7 @@ import { } from '@home/pages/notification/inbox/inbox-notification-dialog.component'; import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; +import { UtilsService } from '@core/services/utils.service'; @Injectable() export class InboxTableConfigResolver implements Resolve> { @@ -48,7 +49,8 @@ export class InboxTableConfigResolver implements Resolve('createdTime', 'common.created-time', this.datePipe, '170px'), new EntityTableColumn('type', 'notification.type', '10%', (notification) => this.translate.instant(NotificationTemplateTypeTranslateMap.get(notification.type).name)), - new EntityTableColumn('subject', 'notification.subject', '30%'), - new EntityTableColumn('text', 'notification.message', '60%') + new EntityTableColumn('subject', 'notification.subject', '30%', + (entity) => this.utilsService.customTranslation(entity.subject, entity.subject)), + new EntityTableColumn('text', 'notification.message', '60%', + (entity) => this.utilsService.customTranslation(entity.text, entity.text)) ); } From d6532d2811e36c2f530e1f9cfb897bffd375223b Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Wed, 19 Jul 2023 16:21:14 +0300 Subject: [PATCH 090/168] Added public-api for websocket services. Exported notification-websocket and websocket services --- ui-ngx/src/app/core/public-api.ts | 2 +- ui-ngx/src/app/core/ws/public-api.ts | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 ui-ngx/src/app/core/ws/public-api.ts diff --git a/ui-ngx/src/app/core/public-api.ts b/ui-ngx/src/app/core/public-api.ts index bbf97cde57..cf36240cea 100644 --- a/ui-ngx/src/app/core/public-api.ts +++ b/ui-ngx/src/app/core/public-api.ts @@ -18,7 +18,7 @@ export * from './api/public-api'; export * from './http/public-api'; export * from './local-storage/local-storage.service'; export * from './services/public-api'; -export * from './ws/telemetry-websocket.service'; +export * from './ws/public-api'; export * from './core.state'; export * from './core.module'; export * from './utils'; diff --git a/ui-ngx/src/app/core/ws/public-api.ts b/ui-ngx/src/app/core/ws/public-api.ts new file mode 100644 index 0000000000..d0f5fa8832 --- /dev/null +++ b/ui-ngx/src/app/core/ws/public-api.ts @@ -0,0 +1,19 @@ +/// +/// 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. +/// + +export * from './notification-websocket.service'; +export * from './telemetry-websocket.service'; +export * from './websocket.service'; From 7bd2df3233f7d5f6faf13097c5feeb5d50b0883d Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 13 Jul 2023 13:35:53 +0300 Subject: [PATCH 091/168] EntitiesTableWidgetComponent set rowPointer if has row/dblrow click action --- .../components/widget/lib/entities-table-widget.component.html | 3 ++- .../components/widget/lib/entities-table-widget.component.ts | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html index d89eaeda27..1844ab3ea5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html @@ -88,7 +88,8 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts index 00ddd527d4..4a5a891703 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts @@ -150,6 +150,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni public displayedColumns: string[] = []; public entityDatasource: EntityDatasource; public noDataDisplayMessageText: string; + public rowPointer: boolean; private setCellButtonAction: boolean; private cellContentCache: Array = []; @@ -278,6 +279,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni this.setCellButtonAction = !!this.ctx.actionsApi.getActionDescriptors('actionCellButton').length; + this.rowPointer = !!this.ctx.actionsApi.getActionDescriptors('rowClick').length || !!this.ctx.actionsApi.getActionDescriptors('rowDoubleClick').length; + if (this.settings.entitiesTitle && this.settings.entitiesTitle.length) { this.entitiesTitlePattern = this.utils.customTranslation(this.settings.entitiesTitle, this.settings.entitiesTitle); } else { From 8a1bbe04cc6ca5cc153f7802d4f46eed0c4b607b Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 19 Jul 2023 16:48:29 +0300 Subject: [PATCH 092/168] legend.component .tb-legend-label add cursor:pointer --- .../app/modules/home/components/widget/lib/legend.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/legend.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/legend.component.scss index ae61340873..a12903ff47 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/legend.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/legend.component.scss @@ -72,6 +72,7 @@ text-align: left; white-space: nowrap; outline: none; + cursor: pointer; &.tb-horizontal { width: 95%; From bcf32658f91faedcad9dfa1731fdde9efa33fc8c Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Thu, 20 Jul 2023 10:08:43 +0300 Subject: [PATCH 093/168] Update js-func.component.html --- ui-ngx/src/app/shared/components/js-func.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/components/js-func.component.html b/ui-ngx/src/app/shared/components/js-func.component.html index e621fe1e50..41e834668e 100644 --- a/ui-ngx/src/app/shared/components/js-func.component.html +++ b/ui-ngx/src/app/shared/components/js-func.component.html @@ -27,7 +27,7 @@ - +
Date: Thu, 20 Jul 2023 11:16:28 +0300 Subject: [PATCH 094/168] UI: Updated file units models --- .../shared/components/unit-input.component.ts | 8 +- ui-ngx/src/assets/model/units.json | 4028 ++++++++--------- 2 files changed, 2014 insertions(+), 2022 deletions(-) diff --git a/ui-ngx/src/app/shared/components/unit-input.component.ts b/ui-ngx/src/app/shared/components/unit-input.component.ts index 01a5db32d0..e70bb0e183 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.ts +++ b/ui-ngx/src/app/shared/components/unit-input.component.ts @@ -24,10 +24,6 @@ import { ResourcesService } from '@core/services/resources.service'; const unitsModels = '/assets/model/units.json'; -interface UnitsJson { - units: Array; -} - @Component({ selector: 'tb-unit-input', templateUrl: './unit-input.component.html', @@ -151,8 +147,8 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit { private unitsConstant(): Observable> { if (this.fetchUnits$ === null) { - this.fetchUnits$ = this.resourcesService.loadJsonResource(unitsModels).pipe( - map(units => units.units.map(u => ({ + this.fetchUnits$ = this.resourcesService.loadJsonResource>(unitsModels).pipe( + map(units => units.map(u => ({ symbol: u.symbol, name: this.translate.instant(u.name), tags: u.tags diff --git a/ui-ngx/src/assets/model/units.json b/ui-ngx/src/assets/model/units.json index 720719b366..988c3f34ce 100644 --- a/ui-ngx/src/assets/model/units.json +++ b/ui-ngx/src/assets/model/units.json @@ -1,2017 +1,2013 @@ +[{ + "name": "unit.millimeter", + "symbol": "mm", + "tags": ["level","height","distance","length","width","gap","depth","millimeter","millimeters","rainfall","precipitation", + "displacement","position","movement","transition","mm"] +}, { - "units": [ - { - "name": "unit.millimeter", - "symbol": "mm", - "tags": ["level","height","distance","length","width","gap","depth","millimeter","millimeters","rainfall","precipitation", - "displacement","position","movement","transition","mm"] - }, - { - "name": "unit.centimeter", - "symbol": "cm", - "tags": ["level","height","distance","length","width","gap","depth","centimeter","centimeters","rainfall","precipitation", - "displacement","position","movement","transition","cm"] - }, - { - "name": "unit.angstrom", - "symbol": "Å", - "tags": ["level","height","distance","length","width","gap","depth","atomic scale","atomic distance","nanoscale", - "angstrom","angstroms","Å"] - }, - { - "name": "unit.nanometer", - "symbol": "nm", - "tags": ["level","height","distance","length","width","gap","depth","nanoscale","atomic scale","molecular scale", - "nanometer","nanometers","nm"] - }, - { - "name": "unit.micrometer", - "symbol": "µm", - "tags": ["level","height","distance","length","width","gap","depth","microns","micrometer","micrometers","µm"] - }, - { - "name": "unit.meter", - "symbol": "m", - "tags": ["level","height","distance","length","width","gap","depth","meter","meters","m"] - }, - { - "name": "unit.kilometer", - "symbol": "km", - "tags": ["distance","height","length","width","gap","depth","kilometer","kilometers","km"] - }, - { - "name": "unit.inch", - "symbol": "in", - "tags": ["level","height","distance","length","width","gap","depth","inch","inches","in"] - }, - { - "name": "unit.foot", - "symbol": "ft", - "tags": ["level","height","distance","length","width","gap","depth","foot","feet","ft"] - }, - { - "name": "unit.yard", - "symbol": "yd", - "tags": ["level","height","distance","length","width","gap","depth","yard","yards","yd"] - }, - { - "name": "unit.mile", - "symbol": "mi", - "tags": ["level","height","distance","length","width","gap","depth","mile","miles","mi"] - }, - { - "name": "unit.nautical-mile", - "symbol": "nm", - "tags": ["level","height","distance","length","width","gap","depth","nautical mile","nm"] - }, - { - "name": "unit.astronomical-unit", - "symbol": "AU", - "tags": ["distance","celestial bodies","solar system","AU"] - }, - { - "name": "unit.reciprocal-metre", - "symbol": "m⁻¹", - "tags": ["wavenumber","wave density","wave frequency","m⁻¹"] - }, - { - "name": "unit.meter-per-meter", - "symbol": "m/m", - "tags": ["ratio of length to length","meter per meter","m/m"] - }, - { - "name": "unit.steradian", - "symbol": "sr", - "tags": ["solid angle","spatial extent","steradian","sr"] - }, - { - "name": "unit.thou", - "symbol": "thou", - "tags": ["length","measurement","thou"] - }, - { - "name": "unit.barleycorn", - "symbol": "barleycorn", - "tags": ["length","shoe size","barleycorn"] - }, - { - "name": "unit.hand", - "symbol": "hand", - "tags": ["length","horse measurement","hand"] - }, - { - "name": "unit.chain", - "symbol": "ch", - "tags": ["length","land surveying","ch"] - }, - { - "name": "unit.furlong", - "symbol": "fur", - "tags": ["length","land surveying","fur"] - }, - { - "name": "unit.league", - "symbol": "league", - "tags": ["length","historical measurement","league"] - }, - { - "name": "unit.fathom", - "symbol": "fathom", - "tags": ["depth","nautical measurement","fathom"] - }, - { - "name": "unit.cable", - "symbol": "cable", - "tags": ["distance","nautical measurement","cable"] - }, - { - "name": "unit.link", - "symbol": "link", - "tags": ["length","land surveying","link"] - }, - { - "name": "unit.rod", - "symbol": "rod", - "tags": ["length","land surveying","rod"] - }, - { - "name": "unit.nanogram", - "symbol": "ng", - "tags": ["mass","weight","heaviness","load","nanogram","nanograms","ng"] - }, - { - "name": "unit.microgram", - "symbol": "μg", - "tags": ["mass","weight","heaviness","load","μg","microgram"] - }, - { - "name": "unit.milligram", - "symbol": "mg", - "tags": ["mass","weight","heaviness","load","milligram","miligrams","mg"] - }, - { - "name": "unit.gram", - "symbol": "g", - "tags": ["mass","weight","heaviness","load","gram","grams","g"] - }, - { - "name": "unit.kilogram", - "symbol": "kg", - "tags": ["mass","weight","heaviness","load","kilogram","kilograms","kg"] - }, - { - "name": "unit.tonne", - "symbol": "t", - "tags": ["mass","weight","heaviness","load","tonne","tons","t"] - }, - { - "name": "unit.ounce", - "symbol": "oz", - "tags": ["mass","weight","heaviness","load","ounce","ounces","oz"] - }, - { - "name": "unit.pound", - "symbol": "lb", - "tags": ["mass","weight","heaviness","load","pound","pounds","lb"] - }, - { - "name": "unit.stone", - "symbol": "st", - "tags": ["mass","weight","heaviness","load","stone","stones","st"] - }, - { - "name": "unit.hundredweight-count", - "symbol": "cwt", - "tags": ["mass","weight","heaviness","load","hundredweight count","cwt"] - }, - { - "name": "unit.short-tons", - "symbol": "short tons", - "tags": ["mass","weight","heaviness","load","short ton","short tons"] - }, - { - "name": "unit.dalton", - "symbol": "Da", - "tags": ["atomic mass unit","AMU","unified atomic mass unit","dalton","Da"] - }, - { - "name": "unit.grain", - "symbol": "gr", - "tags": ["mass","measurement","grain","gr"] - }, - { - "name": "unit.drachm", - "symbol": "dr", - "tags": ["mass","measurement","drachm","dr"] - }, - { - "name": "unit.quarter", - "symbol": "qr", - "tags": ["mass","measurement","quarter","qr"] - }, - { - "name": "unit.slug", - "symbol": "slug", - "tags": ["mass","measurement","slug"] - }, - { - "name": "unit.carat", - "symbol": "ct", - "tags": ["gemstone","pearl","jewelry","carat","ct"] - }, - { - "name": "unit.cubic-millimeter", - "symbol": "mm³", - "tags": ["volume","capacity","extent","cubic millimeter","mm³"] - }, - { - "name": "unit.cubic-centimeter", - "symbol": "cm³", - "tags": ["volume","capacity","extent","cubic centimeter","cubic centimeters","cm³"] - }, - { - "name": "unit.cubic-meter", - "symbol": "m³", - "tags": ["volume","capacity","extent","cubic meter","cubic meters","m³"] - }, - { - "name": "unit.cubic-kilometer", - "symbol": "km³", - "tags": ["volume","capacity","extent","cubic kilometer","cubic kilometers","km³"] - }, - { - "name": "unit.microliter", - "symbol": "µL", - "tags": ["volume","liquid measurement","microliter","µL"] - }, - { - "name": "unit.milliliter", - "symbol": "mL", - "tags": ["volume","capacity","extent","milliliter","milliliters","mL"] - }, - { - "name": "unit.liter", - "symbol": "l", - "tags": ["volume","capacity","extent","liter","liters","l"] - }, - { - "name": "unit.hectoliter", - "symbol": "hl", - "tags": ["volume","capacity","extent","hectoliter","hectoliters","hl"] - }, - { - "name": "unit.cubic-inch", - "symbol": "in³", - "tags": ["volume","capacity","extent","cubic inch","cubic inches","in³"] - }, - { - "name": "unit.cubic-foot", - "symbol": "ft³", - "tags": ["volume","capacity","extent","cubic foot","cubic feet","ft³"] - }, - { - "name": "unit.cubic-yard", - "symbol": "yd³", - "tags": ["volume","capacity","extent","cubic yard","cubic yards","yd³"] - }, - { - "name": "unit.fluid-ounce", - "symbol": "fl-oz", - "tags": ["volume","capacity","extent","fluid ounce","fluid ounces","fl-oz"] - }, - { - "name": "unit.pint", - "symbol": "pt", - "tags": ["volume","capacity","extent","pint","pints","pt"] - }, - { - "name": "unit.quart", - "symbol": "qt", - "tags": ["volume","capacity","extent","quart","quarts","qt"] - }, - { - "name": "unit.gallon", - "symbol": "gal", - "tags": ["volume","capacity","extent","gallon","gallons","gal"] - }, - { - "name": "unit.oil-barrels", - "symbol": "bbl", - "tags": ["volume","capacity","extent","oil barrel","oil barrels","bbl"] - }, - { - "name": "unit.cubic-meter-per-kilogram", - "symbol": "m³/kg", - "tags": ["specific volume","volume per unit mass","cubic meter per kilogram","m³/kg"] - }, - { - "name": "unit.gill", - "symbol": "gi", - "tags": ["volume","liquid measurement","gi"] - }, - { - "name": "unit.hogshead", - "symbol": "hhd", - "tags": ["volume","liquid measurement","hhd"] - }, - { - "name": "unit.teaspoon", - "symbol": "tsp", - "tags": ["volume","cooking measurement","tsp"] - }, - { - "name": "unit.tablespoon", - "symbol": "tbsp", - "tags": ["volume","cooking measurement","tbsp"] - }, - { - "name": "unit.cup", - "symbol": "cup", - "tags": ["volume","cooking measurement","cup"] - }, - { - "name": "unit.celsius", - "symbol": "°C", - "tags": ["temperature","heat","cold","warmth","degrees","celsius","shipment condition","°C"] - }, - { - "name": "unit.kelvin", - "symbol": "K", - "tags": ["temperature","heat","cold","warmth","degrees","kelvin","K","color quality","white balance","color temperature"] - }, - { - "name": "unit.rankine", - "symbol": "°R", - "tags": ["temperature","heat","cold","warmth","Rankine","°R"] - }, - { - "name": "unit.fahrenheit", - "symbol": "°F", - "tags": ["temperature","heat","cold","warmth","degrees","fahrenheit","°F"] - }, - { - "name": "unit.meter-per-second", - "symbol": "m/s", - "tags": ["speed","velocity","pace","meter per second","m/s","peak","peak to peak","root mean square (RMS)", - "vibration","wind speed","weather"] - }, - { - "name": "unit.kilometer-per-hour", - "symbol": "km/h", - "tags": ["speed","velocity","pace","kilometer per hour","km/h"] - }, - { - "name": "unit.foot-per-second", - "symbol": "ft/s", - "tags": ["speed","velocity","pace","foot per second","ft/s"] - }, - { - "name": "unit.mile-per-hour", - "symbol": "mph", - "tags": ["speed","velocity","pace","mile per hour","mph"] - }, - { - "name": "unit.knot", - "symbol": "kt", - "tags": ["speed","velocity","pace","knot","knots","kt"] - }, - { - "name": "unit.millimeters-per-minute", - "symbol": "mm/min", - "tags": ["feed rate","cutting feed rate","millimeters per minute","mm/min"] - }, - { - "name": "unit.kilometer-per-hour-squared", - "symbol": "km/h²", - "tags": ["acceleration","rate of change of velocity","kilometer per hour squared","km/h²"] - }, - { - "name": "unit.foot-per-second-squared", - "symbol": "ft/s²", - "tags": ["acceleration","rate of change of velocity","foot per second squared","ft/s²"] - }, - { - "name": "unit.pascal", - "symbol": "Pa", - "tags": ["pressure","force","compression","tension","pascal","pascals","Pa","atmospheric pressure","air pressure", - "weather","altitude","flight"] - }, - { - "name": "unit.kilopascal", - "symbol": "kPa", - "tags": ["pressure","force","compression","tension","kilopascal","kilopascals","kPa"] - }, - { - "name": "unit.megapascal", - "symbol": "MPa", - "tags": ["pressure","force","compression","tension","megapascal","megapascals","MPa"] - }, - { - "name": "unit.gigapascal", - "symbol": "GPa", - "tags": ["pressure","force","compression","tension","gigapascal","gigapascals","GPa"] - }, - { - "name": "unit.millibar", - "symbol": "mbar", - "tags": ["pressure","force","compression","tension","millibar","millibars","mbar"] - }, - { - "name": "unit.bar", - "symbol": "bar", - "tags": ["pressure","force","compression","tension","bar","bars"] - }, - { - "name": "unit.kilobar", - "symbol": "kbar", - "tags": ["pressure","force","compression","tension","kilobar","kilobars","kbar"] - }, - { - "name": "unit.newton", - "symbol": "N", - "tags": ["force","pressure","newton","newtons","N","push","pull","weight","gravity","N"] - }, - { - "name": "unit.newton-meter", - "symbol": "Nm", - "tags": ["torque","rotational force","newton meter","Nm"] - }, - { - "name": "unit.foot-pounds", - "symbol": "ft·lbf", - "tags": ["torque","rotational force","foot-pound","foot-pounds","ft·lbf"] - }, - { - "name": "unit.inch-pounds", - "symbol": "in·lbf", - "tags": ["torque","rotational force","inch-pounds","inch-pound","in·lbf"] - }, - { - "name": "unit.newton-per-meter", - "symbol": "N/m", - "tags": ["linear density","force per unit length","newton per meter","N/m"] - }, - { - "name": "unit.atmospheres", - "symbol": "atm", - "tags": ["pressure","force","compression","tension","atmosphere","atmospheres","atmospheric pressure","atm"] - }, - { - "name": "unit.pounds-per-square-inch", - "symbol": "psi", - "tags": ["pressure","force","compression","tension","pounds per square inch","psi"] - }, - { - "name": "unit.torr", - "symbol": "Torr", - "tags": ["pressure","force","compression","tension","vacuum pressure","torr"] - }, - { - "name": "unit.inches-of-mercury", - "symbol": "inHg", - "tags": ["pressure","force","compression","tension","vacuum pressure","inHg","atmospheric pressure","barometric pressure"] - }, - { - "name": "unit.pascal-per-square-meter", - "symbol": "Pa/m²", - "tags": ["pressure","stress","mechanical strength","pascal per square meter","Pa/m²"] - }, - { - "name": "unit.pound-per-square-inch", - "symbol": "psi/in²", - "tags": ["pressure","stress","mechanical strength","pound per square inch","psi/in²"] - }, - { - "name": "unit.newton-per-square-meter", - "symbol": "N/m²", - "tags": ["pressure","stress","mechanical strength","newton per square meter","N/m²"] - }, - { - "name": "unit.kilogram-force-per-square-meter", - "symbol": "kgf/m²", - "tags": ["pressure","stress","mechanical strength","kilogram-force per square meter","kgf/m²"] - }, - { - "name": "unit.pascal-per-square-centimeter", - "symbol": "Pa/cm²", - "tags": ["pressure","stress","mechanical strength","pascal per square centimeter","Pa/cm²"] - }, - { - "name": "unit.ton-force-per-square-inch", - "symbol": "tonf/in²", - "tags": ["pressure","stress","mechanical strength","ton-force per square inch","tonf/in²"] - }, - { - "name": "unit.kilonewton-per-square-meter", - "symbol": "kN/m²", - "tags": ["stress","pressure","mechanical strength","kilonewton per square meter","kN/m²"] - }, - { - "name": "unit.newton-per-square-millimeter", - "symbol": "N/mm²", - "tags": ["stress","pressure","mechanical strength","newton per square millimeter","N/mm²"] - }, - { - "name": "unit.microjoule", - "symbol": "μJ", - "tags": ["energy","microjoule","microjoules","μJ"] - }, - { - "name": "unit.millijoule", - "symbol": "mJ", - "tags": ["energy","millijoule","millijoules","mJ"] - }, - { - "name": "unit.joule", - "symbol": "J", - "tags": ["joule","joules","energy","work done","heat","electricity","mechanical work"] - }, - { - "name": "unit.kilojoule", - "symbol": "kJ", - "tags": ["energy","kilojoule","kilojoules","kJ"] - }, - { - "name": "unit.megajoule", - "symbol": "MJ", - "tags": ["energy","megajoule","megajoules","MJ"] - }, - { - "name": "unit.gigajoule", - "symbol": "GJ", - "tags": ["energy","gigajoule","gigajoules","GJ"] - }, - { - "name": "unit.watt-hour", - "symbol": "Wh", - "tags": ["energy","watt-hour","watt-hours","energy usage","power consumption","energy consumption","electricity usage"] - }, - { - "name": "unit.kilowatt-hour", - "symbol": "kWh", - "tags": ["energy","kilowatt-hour","kilowatt-hours","energy usage","power consumption","energy consumption","electricity usage"] - }, - { - "name": "unit.electron-volts", - "symbol": "eV", - "tags": ["energy","subatomic particles","radiation"] - }, - { - "name": "unit.joules-per-coulomb", - "symbol": "J/C", - "tags": ["electrical potential energy","voltage","joules per coulomb","J/C"] - }, - { - "name": "unit.british-thermal-unit", - "symbol": "BTU", - "tags": ["energy","heat","work done","british thermal unit","british thermal units","BTU"] - }, - { - "name": "unit.foot-pound", - "symbol": "ft·lb", - "tags": ["energy","foot-pound","foot-pounds","ft·lb","ft⋅lbf"] - }, - { - "name": "unit.calorie", - "symbol": "Cal", - "tags": ["energy","food energy","Calorie","Calories","Cal"] - }, - { - "name": "unit.small-calorie", - "symbol": "cal", - "tags": ["energy","small calorie","calories","cal"] - }, - { - "name": "unit.kilocalorie", - "symbol": "kcal", - "tags": ["energy","small calorie","kilocalories","kcal"] - }, - { - "name": "unit.joule-per-kelvin", - "symbol": "J/K", - "tags": ["specific heat capacity","heat capacity per unit temperature","joule per kelvin","J/K"] - }, - { - "name": "unit.joule-per-kilogram-kelvin", - "symbol": "J/(kg·K)", - "tags": ["specific heat capacity","heat capacity per unit mass and temperature","joule per kilogram-kelvin","J/(kg·K)"] - }, - { - "name": "unit.joule-per-kilogram", - "symbol": "J/kg", - "tags": ["specific energy","specific energy capacity","joule per kilogram","J/kg"] - }, - { - "name": "unit.watt-per-meter-kelvin", - "symbol": "W/(m·K)", - "tags": ["thermal conductivity","watt per meter-kelvin","W/(m·K)"] - }, - { - "name": "unit.joule-per-cubic-meter", - "symbol": "J/m³", - "tags": ["energy density","joule per cubic meter","J/m³"] - }, - { - "name": "unit.therm", - "symbol": "thm", - "tags": ["energy","natural gas consumption","BTU","therm","thm"] - }, - { - "name": "unit.electric-dipole-moment", - "symbol": "C·m", - "tags": ["electric dipole","dipole moment","coulomb meter","C·m"] - }, - { - "name": "unit.magnetic-dipole-moment", - "symbol": "A·m²", - "tags": ["magnetic dipole","dipole moment","ampere square meter","A·m²"] - }, - { - "name": "unit.debye", - "symbol": "D", - "tags": ["polarization","electric dipole moment","debye","D"] - }, - { - "name": "unit.coulomb-per-square-meter-per-volt", - "symbol": "C·m²/V", - "tags": ["polarization","electric field","coulomb per square meter per volt","C·m²/V"] - }, - { - "name": "unit.milliwatt", - "symbol": "mW", - "tags": ["power","horsepower","performance","milliwatt","milliwatts","electricity","mW"] - }, - { - "name": "unit.microwatt", - "symbol": "μW", - "tags": ["power","horsepower","performance","microwatt","microwatts","electricity","μW"] - }, - { - "name": "unit.watt", - "symbol": "W", - "tags": ["power","horsepower","performance","watt","watts","electricity","W"] - }, - { - "name": "unit.kilowatt", - "symbol": "kW", - "tags": ["power","horsepower","performance","kilowatt","kilowatts","electricity","kW"] - }, - { - "name": "unit.megawatt", - "symbol": "MW", - "tags": ["power","horsepower","performance","megawatt","megawatts","electricity","MW"] - }, - { - "name": "unit.gigawatt", - "symbol": "GW", - "tags": ["power","horsepower","performance","gigawatt","gigawatts","electricity","GW"] - }, - { - "name": "unit.metric-horsepower", - "symbol": "PS", - "tags": ["power","performance","metric horsepower","PS"] - }, - { - "name": "unit.milliwatt-per-square-centimeter", - "symbol": "mW/cm²", - "tags": ["power density","radiation intensity","sunlight intensity","signal power","intensity", - "milliwatts per square centimeter","UV Intensity","mW/cm²"] - }, - { - "name": "unit.watt-per-square-centimeter", - "symbol": "W/cm²", - "tags": ["power density","intensity of power","watts per square centimeter","W/cm²"] - }, - { - "name": "unit.kilowatt-per-square-centimeter", - "symbol": "kW/cm²", - "tags": ["power density","intensity of power","kilowatts per square centimeter","kW/cm²"] - }, - { - "name": "unit.milliwatt-per-square-meter", - "symbol": "mW/m²", - "tags": ["power density","intensity of power","milliwatts per square meter","mW/m²"] - }, - { - "name": "unit.watt-per-square-meter", - "symbol": "W/m²", - "tags": ["power density","intensity of power","watts per square meter","W/m²"] - }, - { - "name": "unit.kilowatt-per-square-meter", - "symbol": "kW/m²", - "tags": ["power density","intensity of power","kilowatts per square meter","kW/m²"] - }, - { - "name": "unit.watt-per-square-inch", - "symbol": "W/in²", - "tags": ["power density","intensity of power","watts per square inch","W/in²"] - }, - { - "name": "unit.kilowatt-per-square-inch", - "symbol": "kW/in²", - "tags": ["power density","intensity of power","kilowatts per square inch","kW/in²"] - }, - { - "name": "unit.horsepower", - "symbol": "hp", - "tags": ["power","horsepower","performance","electricity","horsepowers","hp"] - }, - { - "name": "unit.btu-per-hour", - "symbol": "BTU/h", - "tags": ["power","heat transfer","thermal energy","HVAC","BTU/h"] - }, - { - "name": "unit.coulomb", - "symbol": "C", - "tags": ["charge","electricity","electrostatics","Coulomb","C"] - }, - { - "name": "unit.millicoulomb", - "symbol": "mC", - "tags": ["charge","electricity","electrostatics","millicoulombs","mC"] - }, - { - "name": "unit.microcoulomb", - "symbol": "µC", - "tags": ["charge","electricity","electrostatics","microcoulomb","µC"] - }, - { - "name": "unit.picocoulomb", - "symbol": "pC", - "tags": ["charge","electricity","electrostatics","picocoulomb","pC"] - }, - { - "name": "unit.coulomb-per-meter", - "symbol": "C/m", - "tags": ["electric displacement field per length","coulomb per meter","C/m"] - }, - { - "name": "unit.coulomb-per-cubic-meter", - "symbol": "C/m³", - "tags": ["electric charge density","coulomb per cubic meter","C/m³"] - }, - { - "name": "unit.coulomb-per-square-meter", - "symbol": "C/m²", - "tags": ["electric surface charge density","coulomb per square meter","C/m²"] - }, - { - "name": "unit.square-millimeter", - "symbol": "mm²", - "tags": ["area","lot","zone","space","region","square millimeter","square millimeters","mm²","sq-mm"] - }, - { - "name": "unit.square-centimeter", - "symbol": "cm²", - "tags": ["area","lot","zone","space","region","square centimeter","square centimeters","cm²","sq-cm"] - }, - { - "name": "unit.square-meter", - "symbol": "m²", - "tags": ["area","lot","zone","space","region","square meter","square meters","m²","sq-m"] - }, - { - "name": "unit.hectare", - "symbol": "ha", - "tags": ["area","lot","zone","space","region","hectare","hectares","ha"] - }, - { - "name": "unit.square-kilometer", - "symbol": "km²", - "tags": ["area","lot","zone","space","region","square kilometer","square kilometers","km²","sq-km"] - }, - { - "name": "unit.square-inch", - "symbol": "in²", - "tags": ["area","lot","zone","space","region","square inch","square inches","in²","sq-in"] - }, - { - "name": "unit.square-foot", - "symbol": "ft²", - "tags": ["area","lot","zone","space","region","square foot","square feet","ft²","sq-ft"] - }, - { - "name": "unit.square-yard", - "symbol": "yd²", - "tags": ["area","lot","zone","space","region","square yard","square yards","yd²","sq-yd"] - }, - { - "name": "unit.acre", - "symbol": "a", - "tags": ["area","lot","zone","space","region","acre","acres","a"] - }, - { - "name": "unit.square-mile", - "symbol": "ml²", - "tags": ["area","lot","zone","space","region","square mile","square miles","ml²","sq-mi"] - }, - { - "name": "unit.are", - "symbol": "are", - "tags": ["area","land measurement","are"] - }, - { - "name": "unit.barn", - "symbol": "barn", - "tags": ["cross-sectional area","particle physics","nuclear physics","barn"] - }, - { - "name": "unit.circular-inch", - "symbol": "circin", - "tags": ["area","circular measurement","circular inch","circin"] - }, - { - "name": "unit.milliampere-hour", - "symbol": "mAh", - "tags": ["electric current","current flow","electric charge","current capacity","flow of electricity", - "electrical flow","milliampere-hour","milliampere-hours","mAh"] - }, - { - "name": "unit.ampere-hours", - "symbol": "Ah", - "tags": ["electric current","current flow","electric charge","current capacity","flow of electricity", - "electrical flow","ampere","ampere-hours","Ah"] - }, - { - "name": "unit.kiloampere-hours", - "symbol": "kAh", - "tags": ["electric current","current flow","electric charge","current capacity","flow of electricity","electrical flow", - "kiloampere-hours","kiloampere-hour","kAh"] - }, - { - "name": "unit.nanoampere", - "symbol": "nA", - "tags": ["current","amperes","nanoampere","nA"] - }, - { - "name": "unit.picoampere", - "symbol": "pA", - "tags": ["current","amperes","picoampere","pA"] - }, - { - "name": "unit.microampere", - "symbol": "μA", - "tags": ["electric current","microampere","microamperes","μA"] - }, - { - "name": "unit.milliampere", - "symbol": "mA", - "tags": ["electric current","milliampere","milliamperes","mA"] - }, - { - "name": "unit.ampere", - "symbol": "A", - "tags": ["electric current","current flow","flow of electricity","electrical flow","ampere","amperes","amperage","A"] - }, - { - "name": "unit.kiloamperes", - "symbol": "kA", - "tags": ["electric current","current flow","kiloamperes","kA"] - }, - { - "name": "unit.microampere-per-square-centimeter", - "symbol": "µA/cm²", - "tags": ["Current density","microampere per square centimeter","µA/cm²"] - }, - { - "name": "unit.ampere-per-square-meter", - "symbol": "A/m²", - "tags": ["current density","current per unit area","ampere per square meter","A/m²"] - }, - { - "name": "unit.ampere-per-meter", - "symbol": "A/m", - "tags": ["magnetic field strength","magnetic field intensity","ampere per meter","A/m"] - }, - { - "name": "unit.oersted", - "symbol": "Oe", - "tags": ["magnetic field","oersted","Oe"] - }, - { - "name": "unit.bohr-magneton", - "symbol": "μB", - "tags": ["atomic physics","magnetic moment","bohr magneton","μB"] - }, - { - "name": "unit.ampere-meter-squared", - "symbol": "A·m²", - "tags": ["magnetic moment","dipole moment","ampere-meter squared","A·m²"] - }, - { - "name": "unit.ampere-meter", - "symbol": "A·m", - "tags": ["magnetic field","current loop","ampere-meter","A·m"] - }, - { - "name": "unit.nanovolt", - "symbol": "nV", - "tags": ["voltage","volts","nanovolt","nV"] - }, - { - "name": "unit.picovolt", - "symbol": "pV", - "tags": ["voltage","volts","picovolt","pV"] - }, - { - "name": "unit.millivolts", - "symbol": "mV", - "tags": ["electric potential","electric tension","voltage","millivolt","millivolts","mV"] - }, - { - "name": "unit.microvolts", - "symbol": "μV", - "tags": ["electric potential","electric tension","voltage","microvolt","microvolts","μV"] - }, - { - "name": "unit.volt", - "symbol": "V", - "tags": ["electric potential","electric tension","voltage","volt","volts","V","power source","battery","battery level"] - }, - { - "name": "unit.kilovolts", - "symbol": "kV", - "tags": ["electric potential","electric tension","voltage","kilovolt","kilovolts","kV"] - }, - { - "name": "unit.dbmV", - "symbol": "dBmV", - "tags": ["decibels millivolt","voltage level","signal","dBmV"] - }, - { - "name": "unit.volt-meter", - "symbol": "V·m", - "tags": ["electric flux","volt-meter","V·m"] - }, - { - "name": "unit.kilovolt-meter", - "symbol": "kV·m", - "tags": ["electric flux","kilovolt-meter","kV·m"] - }, - { - "name": "unit.megavolt-meter", - "symbol": "MV·m", - "tags": ["electric flux","megavolt-meter","MV·m"] - }, - { - "name": "unit.microvolt-meter", - "symbol": "µV·m", - "tags": ["electric flux","microvolt-meter","µV·m"] - }, - { - "name": "unit.millivolt-meter", - "symbol": "mV·m", - "tags": ["electric flux","millivolt-meter","mV·m"] - }, - { - "name": "unit.nanovolt-meter", - "symbol": "nV·m", - "tags": ["electric flux","nanovolt-meter","nV·m"] - }, - { - "name": "unit.ohm", - "symbol": "Ω", - "tags": ["electrical resistance","resistance","impedance","ohm"] - }, - { - "name": "unit.microohm", - "symbol": "μΩ", - "tags": ["electrical resistance","resistance","microohm","μΩ"] - }, - { - "name": "unit.milliohm", - "symbol": "mΩ", - "tags": ["electrical resistance","resistance","milliohm","mΩ"] - }, - { - "name": "unit.kilohm", - "symbol": "kΩ", - "tags": ["electrical resistance","resistance","kilohm","kΩ"] - }, - { - "name": "unit.megohm", - "symbol": "MΩ", - "tags": ["electrical resistance","resistance","megohm","MΩ"] - }, - { - "name": "unit.gigohm", - "symbol": "GΩ", - "tags": ["electrical resistance","resistance","gigohm","GΩ"] - }, - { - "name": "unit.hertz", - "symbol": "Hz", - "tags": ["frequency","cycles per second","hertz","Hz"] - }, - { - "name": "unit.kilohertz", - "symbol": "kHz", - "tags": ["frequency","cycles per second","kilohertz","kHz"] - }, - { - "name": "unit.megahertz", - "symbol": "MHz", - "tags": ["frequency","cycles per second","megahertz","MHz"] - }, - { - "name": "unit.gigahertz", - "symbol": "GHz", - "tags": ["frequency","cycles per second","gigahertz","GHz"] - }, - { - "name": "unit.rpm", - "symbol": "RPM", - "tags": ["speed","velocity","cycle","engine","Revolutions Per Minute","RPM","angular velocity","rotation speed"] - }, - { - "name": "unit.candela-per-square-meter", - "symbol": "cd/m²", - "tags": ["brightness","light level","Luminance","Candela per square meter","cd/m²"] - }, - { - "name": "unit.candela", - "symbol": "cd", - "tags": ["light intensity","candle power","luminous intensity","Candela","cd"] - }, - { - "name": "unit.lumen", - "symbol": "lm", - "tags": ["total light output","light power","luminous flux","Lumen","lm"] - }, - { - "name": "unit.lux", - "symbol": "lx", - "tags": ["illumination","light level on a surface","illuminance","Lux","lx"] - }, - { - "name": "unit.foot-candle", - "symbol": "fc", - "tags": ["illuminance","light level","foot-candle","fc"] - }, - { - "name": "unit.lumen-per-square-meter", - "symbol": "lm/m²", - "tags": ["illuminance","light level","lumen per square meter","lm/m²"] - }, - { - "name": "unit.lux-second", - "symbol": "lx·s", - "tags": ["light exposure","illumination time","light dosage","Lux second","lx·s"] - }, - { - "name": "unit.lumen-second", - "symbol": "lm·s", - "tags": ["total light energy","luminous energy","Lumen second","lm·s"] - }, - { - "name": "unit.lumens-per-watt", - "symbol": "lm/W", - "tags": ["lighting efficiency","light output per energy","luminous efficacy","Lumens per watt","lm/W"] - }, - { - "name": "unit.absorbance", - "symbol": "AU", - "tags": ["optical density","light absorption","absorbance","AU"] - }, - { - "name": "unit.mole", - "symbol": "mol", - "tags": ["amount of substance","substance quantity","mole","moles","mol"] - }, - { - "name": "unit.nanomole", - "symbol": "nmol", - "tags": ["amount of substance","substance quantity","concentration","nanomole","nmol"] - }, - { - "name": "unit.micromole", - "symbol": "μmol", - "tags": ["amount of substance","substance quantity","micromole","μmol"] - }, - { - "name": "unit.millimole", - "symbol": "mmol", - "tags": ["amount of substance","substance quantity","millimole","mmol"] - }, - { - "name": "unit.kilomole", - "symbol": "kmol", - "tags": ["amount of substance","substance quantity","kilomole","kmol"] - }, - { - "name": "unit.mole-per-cubic-meter", - "symbol": "mol/m³", - "tags": ["concentration","amount of substance","mole per cubic meter","mol/m³"] - }, - { - "name": "unit.percent", - "symbol": "%", - "tags": ["power source","state of charge (SoC)","battery","battery level","level","humidity","moisture","percentage", - "relative humidity","water content","soil moisture","irrigation","water in soil","soil water content","VWC", - "Volumetric Water Content","Total Harmonic Distortion","THD","power quality","UV Transmittance","%"] - }, - { - "name": "unit.rssi", - "symbol": "rssi", - "tags": ["signal strength","signal level","received signal strength indicator","rssi","dBm"] - }, - { - "name": "unit.ppm", - "symbol": "ppm", - "tags": ["carbon dioxide","co²","carbon monoxide","co","aqi","air quality","total volatile organic compounds","tvoc","ppm"] - }, - { - "name": "unit.ppb", - "symbol": "ppb", - "tags": ["ozone","o³","nitrogen dioxide","no²","sulfur dioxide","so²","aqi","air quality","tvoc","ppb"] - }, - { - "name": "unit.micrograms-per-cubic-meter", - "symbol": "µg/m³", - "tags": ["coarse particulate matter","pm10","fine particulate matter","pm2.5","aqi","air quality", - "total volatile organic compounds","tvoc","micrograms per cubic meter","µg/m³"] - }, - { - "name": "unit.aqi", - "symbol": "aqi", - "tags": ["AQI","air quality index"] - }, - { - "name": "unit.gram-per-cubic-meter", - "symbol": "g/m³", - "tags": ["humidity","moisture","absolute humidity","g/m³"] - }, - { - "name": "unit.gram-per-kilogram", - "symbol": "g/kg", - "tags": ["humidity","moisture","specific humidity","g/kg"] - }, - { - "name": "unit.millimeters-per-second", - "symbol": "mm/s", - "tags": ["velocity","speed","rate of motion","peak","peak to peak","root mean square (RMS)","vibration","mm/s"] - }, - { - "name": "unit.neper", - "symbol": "Np", - "tags": ["logarithmic unit","ratio","gain","loss","attenuation","neper","Np"] - }, - { - "name": "unit.bel", - "symbol": "B", - "tags": ["logarithmic unit","power ratio","intensity ratio","bel","B"] - }, - { - "name": "unit.decibel", - "symbol": "dB", - "tags": ["noise level","sound level","volume","acoustics","decibel","dB"] - }, - { - "name": "unit.meters-per-second-squared", - "symbol": "m/s²", - "tags": ["peak","peak to peak","root mean square (RMS)","vibration","meters per second squared","m/s²"] - }, - { - "name": "unit.becquerel", - "symbol": "Bq", - "tags": ["radioactivity","radiation","becquerel","Bq"] - }, - { - "name": "unit.curie", - "symbol": "Ci", - "tags": ["radioactivity","radiation","curie","Ci"] - }, - { - "name": "unit.gray", - "symbol": "Gy", - "tags": ["radiation dose","gray","Gy"] - }, - { - "name": "unit.sievert", - "symbol": "Sv", - "tags": ["radiation dose","sievert","radiation dose equivalent2","Sv"] - }, - { - "name": "unit.roentgen", - "symbol": "R", - "tags": ["radiation exposure","roentgen","R"] - }, - { - "name": "unit.cps", - "symbol": "cps", - "tags": ["radiation detection","counts per second","cps"] - }, - { - "name": "unit.rad", - "symbol": "Rad", - "tags": ["radiation dose","rad"] - }, - { - "name": "unit.rem", - "symbol": "Rem", - "tags": ["radiation dose equivalent","rem"] - }, - { - "name": "unit.dps", - "symbol": "dps", - "tags": ["radioactive decay","radioactivity","disintegrations per second","dps"] - }, - { - "name": "unit.rutherford", - "symbol": "Rd", - "tags": ["radioactive decay","radioactivity","rutherford","Rd"] - }, - { - "name": "unit.coulombs-per-kilogram", - "symbol": "C/kg", - "tags": ["radiation exposure","dose","coulombs per kilogram","electric charge-to-mass ratio","C/kg"] - }, - { - "name": "unit.becquerels-per-cubic-meter", - "symbol": "Bq/m³", - "tags": ["radioactivity","radiation","becquerels per cubic meter","Bq/m³"] - }, - { - "name": "unit.curies-per-liter", - "symbol": "Ci/L", - "tags": ["radioactivity","radiation","curies per liter","Ci/L"] - }, - { - "name": "unit.becquerels-per-second", - "symbol": "Bq/s", - "tags": ["radioactive decay rate","becquerels per second","Bq/s"] - }, - { - "name": "unit.curies-per-second", - "symbol": "Ci/s", - "tags": ["radioactive decay rate","curies per second","Ci/s"] - }, - { - "name": "unit.gy-per-second", - "symbol": "Gy/s", - "tags": ["absorbed dose rate","radiation dose rate","gray per second","Gy/s"] - }, - { - "name": "unit.watt-per-steradian", - "symbol": "W/sr", - "tags": ["radiant intensity","power per unit solid angle","watt per steradian","W/sr"] - }, - { - "name": "unit.watt-per-square-metre-steradian", - "symbol": "W/(m²·sr)", - "tags": ["radiance","radiant flux density","watt per square metre-steradian","W/(m²·sr)"] - }, - { - "name": "unit.ph-level", - "symbol": "pH", - "tags": ["acidity","alkalinity","neutral","acid","base","pH","soil pH","water quality","water pH"] - }, - { - "name": "unit.turbidity", - "symbol": "NTU", - "tags": ["water turbidity","water clarity","Nephelometric Turbidity Units","NTU"] - }, - { - "name": "unit.mg-per-liter", - "symbol": "mg/L", - "tags": ["dissolved oxygen","water quality","mg/L"] - }, - { - "name": "unit.microsiemens-per-centimeter", - "symbol": "µS/cm", - "tags": ["Electrical conductivity","water quality","soil quality","microsiemens per centimeter","µS/cm"] - }, - { - "name": "unit.millisiemens-per-meter", - "symbol": "mS/m", - "tags": ["Electrical conductivity","water quality","soil quality","millisiemens per meter","mS/m"] - }, - { - "name": "unit.siemens-per-meter", - "symbol": "S/m", - "tags": ["Electrical conductivity","water quality","soil quality","siemens per meter","S/m"] - }, - { - "name": "unit.kilogram-per-cubic-meter", - "symbol": "kg/m³", - "tags": ["density","mass per unit volume","kg/m³"] - }, - { - "name": "unit.gram-per-cubic-centimeter", - "symbol": "g/cm³", - "tags": ["density","mass per unit volume","g/cm³"] - }, - { - "name": "unit.kilogram-per-square-meter", - "symbol": "kg/m²", - "tags": ["density","surface density","areal density","mass per unit area","kg/m²"] - }, - { - "name": "unit.milligram-per-milliliter", - "symbol": "mg/mL", - "tags": ["concentration","mass per volume","mg/mL"] - }, - { - "name": "unit.pound-per-cubic-foot", - "symbol": "lb/ft³", - "tags": ["Density","mass per unit volume","lb/ft³"] - }, - { - "name": "unit.ounces-per-cubic-inch", - "symbol": "oz/in³", - "tags": ["density","mass per unit volume","oz/in³"] - }, - { - "name": "unit.tons-per-cubic-yard", - "symbol": "ton/yd³", - "tags": ["density","mass per unit volume","ton/yd³"] - }, - { - "name": "unit.particle-density", - "symbol": "particles/mL", - "tags": ["particle concentration","count","particles/mL"] - }, - { - "name": "unit.kilometers-per-liter", - "symbol": "km/L", - "tags": ["fuel efficiency","km/L"] - }, - { - "name": "unit.miles-per-gallon", - "symbol": "mpg", - "tags": ["fuel efficiency","mpg"] - }, - { - "name": "unit.liters-per-100-km", - "symbol": "L/100km", - "tags": ["fuel efficiency","L/100km"] - }, - { - "name": "unit.gallons-per-mile", - "symbol": "gal/mi", - "tags": ["fuel efficiency","gal/mi"] - }, - { - "name": "unit.liters-per-hour", - "symbol": "L/hr", - "tags": ["fuel consumption","L/hr"] - }, - { - "name": "unit.gallons-per-hour", - "symbol": "gal/hr", - "tags": ["fuel consumption","gal/hr"] - }, - { - "name": "unit.beats-per-minute", - "symbol": "bpm", - "tags": ["heart rate","pulse","bpm"] - }, - { - "name": "unit.millimeters-of-mercury", - "symbol": "mmHg", - "tags": ["blood pressure","systolic","diastolic","mmHg"] - }, - { - "name": "unit.milligrams-per-deciliter", - "symbol": "mg/dL", - "tags": ["glucose","blood sugar","glucose level","mg/dL"] - }, - { - "name": "unit.g-force", - "symbol": "G", - "tags": ["acceleration","gravity","force","g-load","G"] - }, - { - "name": "unit.kilonewton", - "symbol": "kN", - "tags": ["force","kN"] - }, - { - "name": "unit.kilogram-force", - "symbol": "kgf", - "tags": ["force","kgf"] - }, - { - "name": "unit.pound-force", - "symbol": "lbf", - "tags": ["force","lbf"] - }, - { - "name": "unit.kilopound-force", - "symbol": "klbf", - "tags": ["force","klbf"] - }, - { - "name": "unit.dyne", - "symbol": "dyn", - "tags": ["force","dyn"] - }, - { - "name": "unit.poundal", - "symbol": "pdl", - "tags": ["force","pdl"] - }, - { - "name": "unit.kip", - "symbol": "kip", - "tags": ["force","kip"] - }, - { - "name": "unit.gal", - "symbol": "Gal", - "tags": ["acceleration","gravity","g-force","Gal"] - }, - { - "name": "unit.gravity", - "symbol": "gravity", - "tags": ["acceleration","gravity","g-force"] - }, - { - "name": "unit.hectopascal", - "symbol": "hPa", - "tags": ["atmospheric pressure","air pressure","weather","altitude","flight","hPa"] - }, - { - "name": "unit.atmosphere", - "symbol": "atm", - "tags": ["atmospheric pressure","air pressure","weather","altitude","flight","atm"] - }, - { - "name": "unit.millibars", - "symbol": "mb", - "tags": ["atmospheric pressure","air pressure","weather","altitude","flight","mb"] - }, - { - "name": "unit.inch-of-mercury", - "symbol": "inHg", - "tags": ["atmospheric pressure","air pressure","weather","altitude","flight","inHg","richter"] - }, - { - "name": "unit.richter-scale", - "symbol": "richter", - "tags": ["earthquake","seismic activity","richter"] - }, - { - "name": "unit.second", - "symbol": "s", - "tags": ["time","duration","interval","angle","second","arcsecond","sec"] - }, - { - "name": "unit.minute", - "symbol": "min", - "tags": ["time","duration","interval","angle","minute","arcminute","min"] - }, - { - "name": "unit.hour", - "symbol": "h", - "tags": ["time","duration","interval","h"] - }, - { - "name": "unit.day", - "symbol": "d", - "tags": ["time","duration","interval","d"] - }, - { - "name": "unit.week", - "symbol": "wk", - "tags": ["time","duration","interval","wk"] - }, - { - "name": "unit.month", - "symbol": "mo", - "tags": ["time","duration","interval","mo"] - }, - { - "name": "unit.year", - "symbol": "yr", - "tags": ["time","duration","interval","yr"] - }, - { - "name": "unit.cubic-foot-per-minute", - "symbol": "ft³/min", - "tags": ["airflow","ventilation","HVAC","gas flow rate","CFM","flow rate","fluid flow","cubic foot per minute","ft³/min"] - }, - { - "name": "unit.cubic-meters-per-hour", - "symbol": "m³/hr", - "tags": ["airflow","ventilation","HVAC","gas flow rate","cubic meters per hour","m³/hr"] - }, - { - "name": "unit.cubic-meters-per-second", - "symbol": "m³/s", - "tags": ["airflow","ventilation","HVAC","gas flow rate","cubic meters per second","m³/s"] - }, - { - "name": "unit.liter-per-second", - "symbol": "L/s", - "tags": ["airflow","ventilation","HVAC","gas flow rate","liter per second","L/s"] - }, - { - "name": "unit.liter-per-minute", - "symbol": "L/min", - "tags": ["airflow","ventilation","HVAC","gas flow rate","liter per minute","L/min"] - }, - { - "name": "unit.gallons-per-minute", - "symbol": "GPM", - "tags": ["airflow","ventilation","HVAC","gas flow rate","gallons per minute","GPM"] - }, - { - "name": "unit.cubic-foot-per-second", - "symbol": "ft³/s", - "tags": ["flow rate","fluid flow","cubic foot per second","cubic feet per second","ft³/s"] - }, - { - "name": "unit.milliliters-per-minute", - "symbol": "mL/min", - "tags": ["Flow rate","fluid dynamics","milliliters per minute","mL/min"] - }, - { - "name": "unit.bit", - "symbol": "bit", - "tags": ["data","binary digit","information","bit"] - }, - { - "name": "unit.byte", - "symbol": "B", - "tags": ["data","byte","information","storage","memory","B"] - }, - { - "name": "unit.kilobyte", - "symbol": "KB", - "tags": ["data","kilobyte","KB"] - }, - { - "name": "unit.megabyte", - "symbol": "MB", - "tags": ["data","megabyte","MB"] - }, - { - "name": "unit.gigabyte", - "symbol": "GB", - "tags": ["data","gigabyte","GB"] - }, - { - "name": "unit.terabyte", - "symbol": "TB", - "tags": ["data","terabyte","TB"] - }, - { - "name": "unit.petabyte", - "symbol": "PB", - "tags": ["data","petabyte","PB"] - }, - { - "name": "unit.exabyte", - "symbol": "EB", - "tags": ["data","exabyte","EB"] - }, - { - "name": "unit.zettabyte", - "symbol": "ZB", - "tags": ["data","zettabyte","ZB"] - }, - { - "name": "unit.yottabyte", - "symbol": "YB", - "tags": ["data","yottabyte","YB"] - }, - { - "name": "unit.bit-per-second", - "symbol": "bps", - "tags": ["data transfer rate","bps"] - }, - { - "name": "unit.kilobit-per-second", - "symbol": "kbps", - "tags": ["data transfer rate","kbps"] - }, - { - "name": "unit.megabit-per-second", - "symbol": "Mbps", - "tags": ["data transfer rate","Mbps"] - }, - { - "name": "unit.gigabit-per-second", - "symbol": "Gbps", - "tags": ["data transfer rate","Gbps"] - }, - { - "name": "unit.terabit-per-second", - "symbol": "Tbps", - "tags": ["data transfer rate","Tbps"] - }, - { - "name": "unit.byte-per-second", - "symbol": "B/s", - "tags": ["data transfer rate","B/s"] - }, - { - "name": "unit.kilobyte-per-second", - "symbol": "KB/s", - "tags": ["data transfer rate","KB/s"] - }, - { - "name": "unit.megabyte-per-second", - "symbol": "MB/s", - "tags": ["data transfer rate","MB/s"] - }, - { - "name": "unit.gigabyte-per-second", - "symbol": "GB/s", - "tags": ["data transfer rate","GB/s"] - }, - { - "name": "unit.degree", - "symbol": "deg", - "tags": ["angle","degree","degrees","deg"] - }, - { - "name": "unit.radian", - "symbol": "rad", - "tags": ["angle","radian","radians","rad"] - }, - { - "name": "unit.gradian", - "symbol": "grad", - "tags": ["angle","gradian","grades","grad"] - }, - { - "name": "unit.mil", - "symbol": "mil", - "tags": ["angle","military angle","angular mil","mil"] - }, - { - "name": "unit.revolution", - "symbol": "rev", - "tags": ["angle","revolution","full circle","complete turn","rev"] - }, - { - "name": "unit.siemens", - "symbol": "S", - "tags": ["electrical conductance","conductance","siemens","S"] - }, - { - "name": "unit.millisiemens", - "symbol": "mS", - "tags": ["electrical conductance","conductance","millisiemens","mS"] - }, - { - "name": "unit.microsiemens", - "symbol": "μS", - "tags": ["electrical conductance","conductance","microsiemens","μS"] - }, - { - "name": "unit.kilosiemens", - "symbol": "kS", - "tags": ["electrical conductance","conductance","kilosiemens","kS"] - }, - { - "name": "unit.megasiemens", - "symbol": "MS", - "tags": ["electrical conductance","conductance","megasiemens","MS"] - }, - { - "name": "unit.gigasiemens", - "symbol": "GS", - "tags": ["electrical conductance","conductance","gigasiemens","GS"] - }, - { - "name": "unit.farad", - "symbol": "F", - "tags": ["electric capacitance","capacitance","farad","F"] - }, - { - "name": "unit.millifarad", - "symbol": "mF", - "tags": ["electric capacitance","capacitance","millifarad","mF"] - }, - { - "name": "unit.microfarad", - "symbol": "μF", - "tags": ["electric capacitance","capacitance","microfarad","μF"] - }, - { - "name": "unit.nanofarad", - "symbol": "nF", - "tags": ["electric capacitance","capacitance","nanofarad","nF"] - }, - { - "name": "unit.picofarad", - "symbol": "pF", - "tags": ["electric capacitance","capacitance","picofarad","pF"] - }, - { - "name": "unit.kilofarad", - "symbol": "kF", - "tags": ["electric capacitance","capacitance","kilofarad","kF"] - }, - { - "name": "unit.megafarad", - "symbol": "MF", - "tags": ["electric capacitance","capacitance","megafarad","MF"] - }, - { - "name": "unit.gigafarad", - "symbol": "GF", - "tags": ["electric capacitance","capacitance","gigafarad","GF"] - }, - { - "name": "unit.terfarad", - "symbol": "TF", - "tags": ["electric capacitance","capacitance","terafarad","TF"] - }, - { - "name": "unit.farad-per-meter", - "symbol": "F/m", - "tags": ["electric permittivity","farad per meter","F/m"] - }, - { - "name": "unit.tesla", - "symbol": "T", - "tags": ["magnetic field","magnetic field strength","tesla","T","magnetic flux density"] - }, - { - "name": "unit.gauss", - "symbol": "G", - "tags": ["magnetic field","magnetic field strength","gauss","G","magnetic flux density"] - }, - { - "name": "unit.kilogauss", - "symbol": "kG", - "tags": ["magnetic field","magnetic field strength","kilogauss","kG","magnetic flux density"] - }, - { - "name": "unit.millitesla", - "symbol": "mT", - "tags": ["magnetic field","magnetic field strength","millitesla","mT"] - }, - { - "name": "unit.microtesla", - "symbol": "μT", - "tags": ["magnetic field","magnetic field strength","microtesla","μT"] - }, - { - "name": "unit.nanotesla", - "symbol": "nT", - "tags": ["magnetic field","magnetic field strength","nanotesla","nT"] - }, - { - "name": "unit.kilotesla", - "symbol": "kT", - "tags": ["magnetic field","magnetic field strength","kilotesla","kT"] - }, - { - "name": "unit.megatesla", - "symbol": "MT", - "tags": ["magnetic field","magnetic field strength","megatesla","MT"] - }, - { - "name": "unit.millitesla-square-meters", - "symbol": "millitesla square meters", - "tags": ["magnetic field","millitesla square meters"] - }, - { - "name": "unit.gamma", - "symbol": "γ", - "tags": ["magnetic flux density","gamma","γ"] - }, - { - "name": "unit.lambda", - "symbol": "λ", - "tags": ["wavelength","lambda","λ"] - }, - { - "name": "unit.square-meter-per-second", - "symbol": "m²/s", - "tags": ["kinematic viscosity","m²/s"] - }, - { - "name": "unit.square-centimeter-per-second", - "symbol": "cm²/s", - "tags": ["kinematic viscosity","cm²/s"] - }, - { - "name": "unit.stoke", - "symbol": "St", - "tags": ["kinematic viscosity","stokes","St"] - }, - { - "name": "unit.centistokes", - "symbol": "cSt", - "tags": ["kinematic viscosity","centistokes","cSt"] - }, - { - "name": "unit.square-foot-per-second", - "symbol": "ft²/s", - "tags": ["kinematic viscosity","ft²/s"] - }, - { - "name": "unit.square-inch-per-second", - "symbol": "in²/s", - "tags": ["kinematic viscosity","in²/s"] - }, - { - "name": "unit.pascal-second", - "symbol": "Pa·s", - "tags": ["dynamic viscosity","viscosity","fluid mechanics","pascal-second","Pa·s"] - }, - { - "name": "unit.centipoise", - "symbol": "cP", - "tags": ["viscosity","dynamic viscosity","fluid viscosity","centipoise","cP"] - }, - { - "name": "unit.poise", - "symbol": "P", - "tags": ["viscosity","dynamic viscosity","fluid viscosity","poise","P"] - }, - { - "name": "unit.reynolds", - "symbol": "Re", - "tags": ["fluid flow regime","fluid mechanics","reynolds","Re"] - }, - { - "name": "unit.pound-per-foot-hour", - "symbol": "lb/(ft·h)", - "tags": ["pound per foot-hour","lb/(ft·h)"] - }, - { - "name": "unit.newton-second-per-square-meter", - "symbol": "N·s/m²", - "tags": ["newton second per square meter","N·s/m²"] - }, - { - "name": "unit.dyne-second-per-square-centimeter", - "symbol": "dyn·s/cm²", - "tags": ["dyne second per square centimeter","dyn·s/cm²"] - }, - { - "name": "unit.kilogram-per-meter-second", - "symbol": "kg/(m·s)", - "tags": ["kilogram per meter-second","kg/(m·s)"] - }, - { - "name": "unit.tesla-square-meters", - "symbol": "T/m²", - "tags": ["magnetic flux density","tesla square meters","T/m²"] - }, - { - "name": "unit.maxwell", - "symbol": "Mx", - "tags": ["magnetic flux","magnetic field","maxwell","Mx"] - }, - { - "name": "unit.tesla-per-meter", - "symbol": "T/m", - "tags": ["magnetic field","tesla per meter","T/m"] - }, - { - "name": "unit.gauss-per-centimeter", - "symbol": "G/cm", - "tags": ["magnetic field","gauss per centimeter","G/cm"] - }, - { - "name": "unit.weber", - "symbol": "Wb", - "tags": ["magnetic flux","weber","Wb"] - }, - { - "name": "unit.microweber", - "symbol": "µWb", - "tags": ["magnetic flux","microweber","µWb"] - }, - { - "name": "unit.milliweber", - "symbol": "mWb", - "tags": ["magnetic flux","milliweber","mWb"] - }, - { - "name": "unit.gauss-square-centimeter", - "symbol": "G·cm²", - "tags": ["magnetic flux","gauss-square centimeter","G·cm²"] - }, - { - "name": "unit.kilogauss-square-centimeter", - "symbol": "kG·cm²", - "tags": ["magnetic flux","kilogauss-square centimeter","kG·cm²"] - }, - { - "name": "unit.henry", - "symbol": "H", - "tags": ["inductance","magnetic induction","H"] - }, - { - "name": "unit.millihenry", - "symbol": "mH", - "tags": ["inductance","millihenry","mH"] - }, - { - "name": "unit.microhenry", - "symbol": "µH", - "tags": ["inductance","microhenry","µH"] - }, - { - "name": "unit.nanohenry", - "symbol": "nH", - "tags": ["inductance","nanohenry","nH"] - }, - { - "name": "unit.henry-per-meter", - "symbol": "H/m", - "tags": ["magnetic permeability","henry per meter","H/m"] - }, - { - "name": "unit.tesla-meter-per-ampere", - "symbol": "T·m/A", - "tags": ["magnetic field","Tesla Meter per Ampere","T·m/A","magnetic flux"] - }, - { - "name": "unit.gauss-per-oersted", - "symbol": "G/Oe", - "tags": ["magnetic field","Gauss per Oersted","G/Oe"] - }, - { - "name": "unit.kilogram-per-mole", - "symbol": "kg/mol", - "tags": ["molar mass","kilogram per mole","kg/mol"] - }, - { - "name": "unit.gram-per-mole", - "symbol": "g/mol", - "tags": ["molar mass","gram per mole","g/mol"] - }, - { - "name": "unit.milligram-per-mole", - "symbol": "mg/mol", - "tags": ["molar mass","milligram per mole","mg/mol"] - }, - { - "name": "unit.joule-per-mole", - "symbol": "J/mol", - "tags": ["molar energy","joule per mole","J/mol"] - }, - { - "name": "unit.joule-per-mole-kelvin", - "symbol": "J/(mol·K)", - "tags": ["molar heat capacity","joule per mole-kelvin","J/(mol·K)"] - }, - { - "name": "unit.millivolts-per-meter", - "symbol": "mV/m", - "tags": ["electric field strength","millivolts per meter","mV/m"] - }, - { - "name": "unit.volts-per-meter", - "symbol": "V/m", - "tags": ["electric field strength","volts per meter","V/m"] - }, - { - "name": "unit.kilovolts-per-meter", - "symbol": "kV/m", - "tags": ["electric field strength","kilovolts per meter","kV/m"] - }, - { - "name": "unit.radian-per-second", - "symbol": "rad/s", - "tags": ["angular velocity","rotation speed","rad/s"] - }, - { - "name": "unit.radian-per-second-squared", - "symbol": "rad/s²", - "tags": ["angular acceleration","rotation rate of change","rad/s²"] - }, - { - "name": "unit.revolutions-per-minute-per-second", - "symbol": "rpm/s", - "tags": ["angular acceleration","rotation rate of change","rpm/s"] - }, - { - "name": "unit.revolutions-per-minute-per-second-squared", - "symbol": "rpm/s²", - "tags": ["angular acceleration","rotation rate of change","rpm/s²"] - }, - { - "name": "unit.deg-per-second", - "symbol": "deg/s", - "tags": ["angular velocity","degrees per second","deg/s"] - }, - { - "name": "unit.degrees-brix", - "symbol": "°Bx", - "tags": ["sugar content","fruit ripeness","Bx"] - }, - { - "name": "unit.katal", - "symbol": "kat", - "tags": ["catalytic activity","enzyme activity","kat"] - }, - { - "name": "unit.katal-per-cubic-metre", - "symbol": "kat/m³", - "tags": ["catalytic activity concentration","enzyme concentration","kat/m³"] - } - ] -} + "name": "unit.centimeter", + "symbol": "cm", + "tags": ["level","height","distance","length","width","gap","depth","centimeter","centimeters","rainfall","precipitation", + "displacement","position","movement","transition","cm"] +}, +{ + "name": "unit.angstrom", + "symbol": "Å", + "tags": ["level","height","distance","length","width","gap","depth","atomic scale","atomic distance","nanoscale", + "angstrom","angstroms","Å"] +}, +{ + "name": "unit.nanometer", + "symbol": "nm", + "tags": ["level","height","distance","length","width","gap","depth","nanoscale","atomic scale","molecular scale", + "nanometer","nanometers","nm"] +}, +{ + "name": "unit.micrometer", + "symbol": "µm", + "tags": ["level","height","distance","length","width","gap","depth","microns","micrometer","micrometers","µm"] +}, +{ + "name": "unit.meter", + "symbol": "m", + "tags": ["level","height","distance","length","width","gap","depth","meter","meters","m"] +}, +{ + "name": "unit.kilometer", + "symbol": "km", + "tags": ["distance","height","length","width","gap","depth","kilometer","kilometers","km"] +}, +{ + "name": "unit.inch", + "symbol": "in", + "tags": ["level","height","distance","length","width","gap","depth","inch","inches","in"] +}, +{ + "name": "unit.foot", + "symbol": "ft", + "tags": ["level","height","distance","length","width","gap","depth","foot","feet","ft"] +}, +{ + "name": "unit.yard", + "symbol": "yd", + "tags": ["level","height","distance","length","width","gap","depth","yard","yards","yd"] +}, +{ + "name": "unit.mile", + "symbol": "mi", + "tags": ["level","height","distance","length","width","gap","depth","mile","miles","mi"] +}, +{ + "name": "unit.nautical-mile", + "symbol": "nm", + "tags": ["level","height","distance","length","width","gap","depth","nautical mile","nm"] +}, +{ + "name": "unit.astronomical-unit", + "symbol": "AU", + "tags": ["distance","celestial bodies","solar system","AU"] +}, +{ + "name": "unit.reciprocal-metre", + "symbol": "m⁻¹", + "tags": ["wavenumber","wave density","wave frequency","m⁻¹"] +}, +{ + "name": "unit.meter-per-meter", + "symbol": "m/m", + "tags": ["ratio of length to length","meter per meter","m/m"] +}, +{ + "name": "unit.steradian", + "symbol": "sr", + "tags": ["solid angle","spatial extent","steradian","sr"] +}, +{ + "name": "unit.thou", + "symbol": "thou", + "tags": ["length","measurement","thou"] +}, +{ + "name": "unit.barleycorn", + "symbol": "barleycorn", + "tags": ["length","shoe size","barleycorn"] +}, +{ + "name": "unit.hand", + "symbol": "hand", + "tags": ["length","horse measurement","hand"] +}, +{ + "name": "unit.chain", + "symbol": "ch", + "tags": ["length","land surveying","ch"] +}, +{ + "name": "unit.furlong", + "symbol": "fur", + "tags": ["length","land surveying","fur"] +}, +{ + "name": "unit.league", + "symbol": "league", + "tags": ["length","historical measurement","league"] +}, +{ + "name": "unit.fathom", + "symbol": "fathom", + "tags": ["depth","nautical measurement","fathom"] +}, +{ + "name": "unit.cable", + "symbol": "cable", + "tags": ["distance","nautical measurement","cable"] +}, +{ + "name": "unit.link", + "symbol": "link", + "tags": ["length","land surveying","link"] +}, +{ + "name": "unit.rod", + "symbol": "rod", + "tags": ["length","land surveying","rod"] +}, +{ + "name": "unit.nanogram", + "symbol": "ng", + "tags": ["mass","weight","heaviness","load","nanogram","nanograms","ng"] +}, +{ + "name": "unit.microgram", + "symbol": "μg", + "tags": ["mass","weight","heaviness","load","μg","microgram"] +}, +{ + "name": "unit.milligram", + "symbol": "mg", + "tags": ["mass","weight","heaviness","load","milligram","miligrams","mg"] +}, +{ + "name": "unit.gram", + "symbol": "g", + "tags": ["mass","weight","heaviness","load","gram","grams","g"] +}, +{ + "name": "unit.kilogram", + "symbol": "kg", + "tags": ["mass","weight","heaviness","load","kilogram","kilograms","kg"] +}, +{ + "name": "unit.tonne", + "symbol": "t", + "tags": ["mass","weight","heaviness","load","tonne","tons","t"] +}, +{ + "name": "unit.ounce", + "symbol": "oz", + "tags": ["mass","weight","heaviness","load","ounce","ounces","oz"] +}, +{ + "name": "unit.pound", + "symbol": "lb", + "tags": ["mass","weight","heaviness","load","pound","pounds","lb"] +}, +{ + "name": "unit.stone", + "symbol": "st", + "tags": ["mass","weight","heaviness","load","stone","stones","st"] +}, +{ + "name": "unit.hundredweight-count", + "symbol": "cwt", + "tags": ["mass","weight","heaviness","load","hundredweight count","cwt"] +}, +{ + "name": "unit.short-tons", + "symbol": "short tons", + "tags": ["mass","weight","heaviness","load","short ton","short tons"] +}, +{ + "name": "unit.dalton", + "symbol": "Da", + "tags": ["atomic mass unit","AMU","unified atomic mass unit","dalton","Da"] +}, +{ + "name": "unit.grain", + "symbol": "gr", + "tags": ["mass","measurement","grain","gr"] +}, +{ + "name": "unit.drachm", + "symbol": "dr", + "tags": ["mass","measurement","drachm","dr"] +}, +{ + "name": "unit.quarter", + "symbol": "qr", + "tags": ["mass","measurement","quarter","qr"] +}, +{ + "name": "unit.slug", + "symbol": "slug", + "tags": ["mass","measurement","slug"] +}, +{ + "name": "unit.carat", + "symbol": "ct", + "tags": ["gemstone","pearl","jewelry","carat","ct"] +}, +{ + "name": "unit.cubic-millimeter", + "symbol": "mm³", + "tags": ["volume","capacity","extent","cubic millimeter","mm³"] +}, +{ + "name": "unit.cubic-centimeter", + "symbol": "cm³", + "tags": ["volume","capacity","extent","cubic centimeter","cubic centimeters","cm³"] +}, +{ + "name": "unit.cubic-meter", + "symbol": "m³", + "tags": ["volume","capacity","extent","cubic meter","cubic meters","m³"] +}, +{ + "name": "unit.cubic-kilometer", + "symbol": "km³", + "tags": ["volume","capacity","extent","cubic kilometer","cubic kilometers","km³"] +}, +{ + "name": "unit.microliter", + "symbol": "µL", + "tags": ["volume","liquid measurement","microliter","µL"] +}, +{ + "name": "unit.milliliter", + "symbol": "mL", + "tags": ["volume","capacity","extent","milliliter","milliliters","mL"] +}, +{ + "name": "unit.liter", + "symbol": "l", + "tags": ["volume","capacity","extent","liter","liters","l"] +}, +{ + "name": "unit.hectoliter", + "symbol": "hl", + "tags": ["volume","capacity","extent","hectoliter","hectoliters","hl"] +}, +{ + "name": "unit.cubic-inch", + "symbol": "in³", + "tags": ["volume","capacity","extent","cubic inch","cubic inches","in³"] +}, +{ + "name": "unit.cubic-foot", + "symbol": "ft³", + "tags": ["volume","capacity","extent","cubic foot","cubic feet","ft³"] +}, +{ + "name": "unit.cubic-yard", + "symbol": "yd³", + "tags": ["volume","capacity","extent","cubic yard","cubic yards","yd³"] +}, +{ + "name": "unit.fluid-ounce", + "symbol": "fl-oz", + "tags": ["volume","capacity","extent","fluid ounce","fluid ounces","fl-oz"] +}, +{ + "name": "unit.pint", + "symbol": "pt", + "tags": ["volume","capacity","extent","pint","pints","pt"] +}, +{ + "name": "unit.quart", + "symbol": "qt", + "tags": ["volume","capacity","extent","quart","quarts","qt"] +}, +{ + "name": "unit.gallon", + "symbol": "gal", + "tags": ["volume","capacity","extent","gallon","gallons","gal"] +}, +{ + "name": "unit.oil-barrels", + "symbol": "bbl", + "tags": ["volume","capacity","extent","oil barrel","oil barrels","bbl"] +}, +{ + "name": "unit.cubic-meter-per-kilogram", + "symbol": "m³/kg", + "tags": ["specific volume","volume per unit mass","cubic meter per kilogram","m³/kg"] +}, +{ + "name": "unit.gill", + "symbol": "gi", + "tags": ["volume","liquid measurement","gi"] +}, +{ + "name": "unit.hogshead", + "symbol": "hhd", + "tags": ["volume","liquid measurement","hhd"] +}, +{ + "name": "unit.teaspoon", + "symbol": "tsp", + "tags": ["volume","cooking measurement","tsp"] +}, +{ + "name": "unit.tablespoon", + "symbol": "tbsp", + "tags": ["volume","cooking measurement","tbsp"] +}, +{ + "name": "unit.cup", + "symbol": "cup", + "tags": ["volume","cooking measurement","cup"] +}, +{ + "name": "unit.celsius", + "symbol": "°C", + "tags": ["temperature","heat","cold","warmth","degrees","celsius","shipment condition","°C"] +}, +{ + "name": "unit.kelvin", + "symbol": "K", + "tags": ["temperature","heat","cold","warmth","degrees","kelvin","K","color quality","white balance","color temperature"] +}, +{ + "name": "unit.rankine", + "symbol": "°R", + "tags": ["temperature","heat","cold","warmth","Rankine","°R"] +}, +{ + "name": "unit.fahrenheit", + "symbol": "°F", + "tags": ["temperature","heat","cold","warmth","degrees","fahrenheit","°F"] +}, +{ + "name": "unit.meter-per-second", + "symbol": "m/s", + "tags": ["speed","velocity","pace","meter per second","m/s","peak","peak to peak","root mean square (RMS)", + "vibration","wind speed","weather"] +}, +{ + "name": "unit.kilometer-per-hour", + "symbol": "km/h", + "tags": ["speed","velocity","pace","kilometer per hour","km/h"] +}, +{ + "name": "unit.foot-per-second", + "symbol": "ft/s", + "tags": ["speed","velocity","pace","foot per second","ft/s"] +}, +{ + "name": "unit.mile-per-hour", + "symbol": "mph", + "tags": ["speed","velocity","pace","mile per hour","mph"] +}, +{ + "name": "unit.knot", + "symbol": "kt", + "tags": ["speed","velocity","pace","knot","knots","kt"] +}, +{ + "name": "unit.millimeters-per-minute", + "symbol": "mm/min", + "tags": ["feed rate","cutting feed rate","millimeters per minute","mm/min"] +}, +{ + "name": "unit.kilometer-per-hour-squared", + "symbol": "km/h²", + "tags": ["acceleration","rate of change of velocity","kilometer per hour squared","km/h²"] +}, +{ + "name": "unit.foot-per-second-squared", + "symbol": "ft/s²", + "tags": ["acceleration","rate of change of velocity","foot per second squared","ft/s²"] +}, +{ + "name": "unit.pascal", + "symbol": "Pa", + "tags": ["pressure","force","compression","tension","pascal","pascals","Pa","atmospheric pressure","air pressure", + "weather","altitude","flight"] +}, +{ + "name": "unit.kilopascal", + "symbol": "kPa", + "tags": ["pressure","force","compression","tension","kilopascal","kilopascals","kPa"] +}, +{ + "name": "unit.megapascal", + "symbol": "MPa", + "tags": ["pressure","force","compression","tension","megapascal","megapascals","MPa"] +}, +{ + "name": "unit.gigapascal", + "symbol": "GPa", + "tags": ["pressure","force","compression","tension","gigapascal","gigapascals","GPa"] +}, +{ + "name": "unit.millibar", + "symbol": "mbar", + "tags": ["pressure","force","compression","tension","millibar","millibars","mbar"] +}, +{ + "name": "unit.bar", + "symbol": "bar", + "tags": ["pressure","force","compression","tension","bar","bars"] +}, +{ + "name": "unit.kilobar", + "symbol": "kbar", + "tags": ["pressure","force","compression","tension","kilobar","kilobars","kbar"] +}, +{ + "name": "unit.newton", + "symbol": "N", + "tags": ["force","pressure","newton","newtons","N","push","pull","weight","gravity","N"] +}, +{ + "name": "unit.newton-meter", + "symbol": "Nm", + "tags": ["torque","rotational force","newton meter","Nm"] +}, +{ + "name": "unit.foot-pounds", + "symbol": "ft·lbf", + "tags": ["torque","rotational force","foot-pound","foot-pounds","ft·lbf"] +}, +{ + "name": "unit.inch-pounds", + "symbol": "in·lbf", + "tags": ["torque","rotational force","inch-pounds","inch-pound","in·lbf"] +}, +{ + "name": "unit.newton-per-meter", + "symbol": "N/m", + "tags": ["linear density","force per unit length","newton per meter","N/m"] +}, +{ + "name": "unit.atmospheres", + "symbol": "atm", + "tags": ["pressure","force","compression","tension","atmosphere","atmospheres","atmospheric pressure","atm"] +}, +{ + "name": "unit.pounds-per-square-inch", + "symbol": "psi", + "tags": ["pressure","force","compression","tension","pounds per square inch","psi"] +}, +{ + "name": "unit.torr", + "symbol": "Torr", + "tags": ["pressure","force","compression","tension","vacuum pressure","torr"] +}, +{ + "name": "unit.inches-of-mercury", + "symbol": "inHg", + "tags": ["pressure","force","compression","tension","vacuum pressure","inHg","atmospheric pressure","barometric pressure"] +}, +{ + "name": "unit.pascal-per-square-meter", + "symbol": "Pa/m²", + "tags": ["pressure","stress","mechanical strength","pascal per square meter","Pa/m²"] +}, +{ + "name": "unit.pound-per-square-inch", + "symbol": "psi/in²", + "tags": ["pressure","stress","mechanical strength","pound per square inch","psi/in²"] +}, +{ + "name": "unit.newton-per-square-meter", + "symbol": "N/m²", + "tags": ["pressure","stress","mechanical strength","newton per square meter","N/m²"] +}, +{ + "name": "unit.kilogram-force-per-square-meter", + "symbol": "kgf/m²", + "tags": ["pressure","stress","mechanical strength","kilogram-force per square meter","kgf/m²"] +}, +{ + "name": "unit.pascal-per-square-centimeter", + "symbol": "Pa/cm²", + "tags": ["pressure","stress","mechanical strength","pascal per square centimeter","Pa/cm²"] +}, +{ + "name": "unit.ton-force-per-square-inch", + "symbol": "tonf/in²", + "tags": ["pressure","stress","mechanical strength","ton-force per square inch","tonf/in²"] +}, +{ + "name": "unit.kilonewton-per-square-meter", + "symbol": "kN/m²", + "tags": ["stress","pressure","mechanical strength","kilonewton per square meter","kN/m²"] +}, +{ + "name": "unit.newton-per-square-millimeter", + "symbol": "N/mm²", + "tags": ["stress","pressure","mechanical strength","newton per square millimeter","N/mm²"] +}, +{ + "name": "unit.microjoule", + "symbol": "μJ", + "tags": ["energy","microjoule","microjoules","μJ"] +}, +{ + "name": "unit.millijoule", + "symbol": "mJ", + "tags": ["energy","millijoule","millijoules","mJ"] +}, +{ + "name": "unit.joule", + "symbol": "J", + "tags": ["joule","joules","energy","work done","heat","electricity","mechanical work"] +}, +{ + "name": "unit.kilojoule", + "symbol": "kJ", + "tags": ["energy","kilojoule","kilojoules","kJ"] +}, +{ + "name": "unit.megajoule", + "symbol": "MJ", + "tags": ["energy","megajoule","megajoules","MJ"] +}, +{ + "name": "unit.gigajoule", + "symbol": "GJ", + "tags": ["energy","gigajoule","gigajoules","GJ"] +}, +{ + "name": "unit.watt-hour", + "symbol": "Wh", + "tags": ["energy","watt-hour","watt-hours","energy usage","power consumption","energy consumption","electricity usage"] +}, +{ + "name": "unit.kilowatt-hour", + "symbol": "kWh", + "tags": ["energy","kilowatt-hour","kilowatt-hours","energy usage","power consumption","energy consumption","electricity usage"] +}, +{ + "name": "unit.electron-volts", + "symbol": "eV", + "tags": ["energy","subatomic particles","radiation"] +}, +{ + "name": "unit.joules-per-coulomb", + "symbol": "J/C", + "tags": ["electrical potential energy","voltage","joules per coulomb","J/C"] +}, +{ + "name": "unit.british-thermal-unit", + "symbol": "BTU", + "tags": ["energy","heat","work done","british thermal unit","british thermal units","BTU"] +}, +{ + "name": "unit.foot-pound", + "symbol": "ft·lb", + "tags": ["energy","foot-pound","foot-pounds","ft·lb","ft⋅lbf"] +}, +{ + "name": "unit.calorie", + "symbol": "Cal", + "tags": ["energy","food energy","Calorie","Calories","Cal"] +}, +{ + "name": "unit.small-calorie", + "symbol": "cal", + "tags": ["energy","small calorie","calories","cal"] +}, +{ + "name": "unit.kilocalorie", + "symbol": "kcal", + "tags": ["energy","small calorie","kilocalories","kcal"] +}, +{ + "name": "unit.joule-per-kelvin", + "symbol": "J/K", + "tags": ["specific heat capacity","heat capacity per unit temperature","joule per kelvin","J/K"] +}, +{ + "name": "unit.joule-per-kilogram-kelvin", + "symbol": "J/(kg·K)", + "tags": ["specific heat capacity","heat capacity per unit mass and temperature","joule per kilogram-kelvin","J/(kg·K)"] +}, +{ + "name": "unit.joule-per-kilogram", + "symbol": "J/kg", + "tags": ["specific energy","specific energy capacity","joule per kilogram","J/kg"] +}, +{ + "name": "unit.watt-per-meter-kelvin", + "symbol": "W/(m·K)", + "tags": ["thermal conductivity","watt per meter-kelvin","W/(m·K)"] +}, +{ + "name": "unit.joule-per-cubic-meter", + "symbol": "J/m³", + "tags": ["energy density","joule per cubic meter","J/m³"] +}, +{ + "name": "unit.therm", + "symbol": "thm", + "tags": ["energy","natural gas consumption","BTU","therm","thm"] +}, +{ + "name": "unit.electric-dipole-moment", + "symbol": "C·m", + "tags": ["electric dipole","dipole moment","coulomb meter","C·m"] +}, +{ + "name": "unit.magnetic-dipole-moment", + "symbol": "A·m²", + "tags": ["magnetic dipole","dipole moment","ampere square meter","A·m²"] +}, +{ + "name": "unit.debye", + "symbol": "D", + "tags": ["polarization","electric dipole moment","debye","D"] +}, +{ + "name": "unit.coulomb-per-square-meter-per-volt", + "symbol": "C·m²/V", + "tags": ["polarization","electric field","coulomb per square meter per volt","C·m²/V"] +}, +{ + "name": "unit.milliwatt", + "symbol": "mW", + "tags": ["power","horsepower","performance","milliwatt","milliwatts","electricity","mW"] +}, +{ + "name": "unit.microwatt", + "symbol": "μW", + "tags": ["power","horsepower","performance","microwatt","microwatts","electricity","μW"] +}, +{ + "name": "unit.watt", + "symbol": "W", + "tags": ["power","horsepower","performance","watt","watts","electricity","W"] +}, +{ + "name": "unit.kilowatt", + "symbol": "kW", + "tags": ["power","horsepower","performance","kilowatt","kilowatts","electricity","kW"] +}, +{ + "name": "unit.megawatt", + "symbol": "MW", + "tags": ["power","horsepower","performance","megawatt","megawatts","electricity","MW"] +}, +{ + "name": "unit.gigawatt", + "symbol": "GW", + "tags": ["power","horsepower","performance","gigawatt","gigawatts","electricity","GW"] +}, +{ + "name": "unit.metric-horsepower", + "symbol": "PS", + "tags": ["power","performance","metric horsepower","PS"] +}, +{ + "name": "unit.milliwatt-per-square-centimeter", + "symbol": "mW/cm²", + "tags": ["power density","radiation intensity","sunlight intensity","signal power","intensity", + "milliwatts per square centimeter","UV Intensity","mW/cm²"] +}, +{ + "name": "unit.watt-per-square-centimeter", + "symbol": "W/cm²", + "tags": ["power density","intensity of power","watts per square centimeter","W/cm²"] +}, +{ + "name": "unit.kilowatt-per-square-centimeter", + "symbol": "kW/cm²", + "tags": ["power density","intensity of power","kilowatts per square centimeter","kW/cm²"] +}, +{ + "name": "unit.milliwatt-per-square-meter", + "symbol": "mW/m²", + "tags": ["power density","intensity of power","milliwatts per square meter","mW/m²"] +}, +{ + "name": "unit.watt-per-square-meter", + "symbol": "W/m²", + "tags": ["power density","intensity of power","watts per square meter","W/m²"] +}, +{ + "name": "unit.kilowatt-per-square-meter", + "symbol": "kW/m²", + "tags": ["power density","intensity of power","kilowatts per square meter","kW/m²"] +}, +{ + "name": "unit.watt-per-square-inch", + "symbol": "W/in²", + "tags": ["power density","intensity of power","watts per square inch","W/in²"] +}, +{ + "name": "unit.kilowatt-per-square-inch", + "symbol": "kW/in²", + "tags": ["power density","intensity of power","kilowatts per square inch","kW/in²"] +}, +{ + "name": "unit.horsepower", + "symbol": "hp", + "tags": ["power","horsepower","performance","electricity","horsepowers","hp"] +}, +{ + "name": "unit.btu-per-hour", + "symbol": "BTU/h", + "tags": ["power","heat transfer","thermal energy","HVAC","BTU/h"] +}, +{ + "name": "unit.coulomb", + "symbol": "C", + "tags": ["charge","electricity","electrostatics","Coulomb","C"] +}, +{ + "name": "unit.millicoulomb", + "symbol": "mC", + "tags": ["charge","electricity","electrostatics","millicoulombs","mC"] +}, +{ + "name": "unit.microcoulomb", + "symbol": "µC", + "tags": ["charge","electricity","electrostatics","microcoulomb","µC"] +}, +{ + "name": "unit.picocoulomb", + "symbol": "pC", + "tags": ["charge","electricity","electrostatics","picocoulomb","pC"] +}, +{ + "name": "unit.coulomb-per-meter", + "symbol": "C/m", + "tags": ["electric displacement field per length","coulomb per meter","C/m"] +}, +{ + "name": "unit.coulomb-per-cubic-meter", + "symbol": "C/m³", + "tags": ["electric charge density","coulomb per cubic meter","C/m³"] +}, +{ + "name": "unit.coulomb-per-square-meter", + "symbol": "C/m²", + "tags": ["electric surface charge density","coulomb per square meter","C/m²"] +}, +{ + "name": "unit.square-millimeter", + "symbol": "mm²", + "tags": ["area","lot","zone","space","region","square millimeter","square millimeters","mm²","sq-mm"] +}, +{ + "name": "unit.square-centimeter", + "symbol": "cm²", + "tags": ["area","lot","zone","space","region","square centimeter","square centimeters","cm²","sq-cm"] +}, +{ + "name": "unit.square-meter", + "symbol": "m²", + "tags": ["area","lot","zone","space","region","square meter","square meters","m²","sq-m"] +}, +{ + "name": "unit.hectare", + "symbol": "ha", + "tags": ["area","lot","zone","space","region","hectare","hectares","ha"] +}, +{ + "name": "unit.square-kilometer", + "symbol": "km²", + "tags": ["area","lot","zone","space","region","square kilometer","square kilometers","km²","sq-km"] +}, +{ + "name": "unit.square-inch", + "symbol": "in²", + "tags": ["area","lot","zone","space","region","square inch","square inches","in²","sq-in"] +}, +{ + "name": "unit.square-foot", + "symbol": "ft²", + "tags": ["area","lot","zone","space","region","square foot","square feet","ft²","sq-ft"] +}, +{ + "name": "unit.square-yard", + "symbol": "yd²", + "tags": ["area","lot","zone","space","region","square yard","square yards","yd²","sq-yd"] +}, +{ + "name": "unit.acre", + "symbol": "a", + "tags": ["area","lot","zone","space","region","acre","acres","a"] +}, +{ + "name": "unit.square-mile", + "symbol": "ml²", + "tags": ["area","lot","zone","space","region","square mile","square miles","ml²","sq-mi"] +}, +{ + "name": "unit.are", + "symbol": "are", + "tags": ["area","land measurement","are"] +}, +{ + "name": "unit.barn", + "symbol": "barn", + "tags": ["cross-sectional area","particle physics","nuclear physics","barn"] +}, +{ + "name": "unit.circular-inch", + "symbol": "circin", + "tags": ["area","circular measurement","circular inch","circin"] +}, +{ + "name": "unit.milliampere-hour", + "symbol": "mAh", + "tags": ["electric current","current flow","electric charge","current capacity","flow of electricity", + "electrical flow","milliampere-hour","milliampere-hours","mAh"] +}, +{ + "name": "unit.ampere-hours", + "symbol": "Ah", + "tags": ["electric current","current flow","electric charge","current capacity","flow of electricity", + "electrical flow","ampere","ampere-hours","Ah"] +}, +{ + "name": "unit.kiloampere-hours", + "symbol": "kAh", + "tags": ["electric current","current flow","electric charge","current capacity","flow of electricity","electrical flow", + "kiloampere-hours","kiloampere-hour","kAh"] +}, +{ + "name": "unit.nanoampere", + "symbol": "nA", + "tags": ["current","amperes","nanoampere","nA"] +}, +{ + "name": "unit.picoampere", + "symbol": "pA", + "tags": ["current","amperes","picoampere","pA"] +}, +{ + "name": "unit.microampere", + "symbol": "μA", + "tags": ["electric current","microampere","microamperes","μA"] +}, +{ + "name": "unit.milliampere", + "symbol": "mA", + "tags": ["electric current","milliampere","milliamperes","mA"] +}, +{ + "name": "unit.ampere", + "symbol": "A", + "tags": ["electric current","current flow","flow of electricity","electrical flow","ampere","amperes","amperage","A"] +}, +{ + "name": "unit.kiloamperes", + "symbol": "kA", + "tags": ["electric current","current flow","kiloamperes","kA"] +}, +{ + "name": "unit.microampere-per-square-centimeter", + "symbol": "µA/cm²", + "tags": ["Current density","microampere per square centimeter","µA/cm²"] +}, +{ + "name": "unit.ampere-per-square-meter", + "symbol": "A/m²", + "tags": ["current density","current per unit area","ampere per square meter","A/m²"] +}, +{ + "name": "unit.ampere-per-meter", + "symbol": "A/m", + "tags": ["magnetic field strength","magnetic field intensity","ampere per meter","A/m"] +}, +{ + "name": "unit.oersted", + "symbol": "Oe", + "tags": ["magnetic field","oersted","Oe"] +}, +{ + "name": "unit.bohr-magneton", + "symbol": "μB", + "tags": ["atomic physics","magnetic moment","bohr magneton","μB"] +}, +{ + "name": "unit.ampere-meter-squared", + "symbol": "A·m²", + "tags": ["magnetic moment","dipole moment","ampere-meter squared","A·m²"] +}, +{ + "name": "unit.ampere-meter", + "symbol": "A·m", + "tags": ["magnetic field","current loop","ampere-meter","A·m"] +}, +{ + "name": "unit.nanovolt", + "symbol": "nV", + "tags": ["voltage","volts","nanovolt","nV"] +}, +{ + "name": "unit.picovolt", + "symbol": "pV", + "tags": ["voltage","volts","picovolt","pV"] +}, +{ + "name": "unit.millivolts", + "symbol": "mV", + "tags": ["electric potential","electric tension","voltage","millivolt","millivolts","mV"] +}, +{ + "name": "unit.microvolts", + "symbol": "μV", + "tags": ["electric potential","electric tension","voltage","microvolt","microvolts","μV"] +}, +{ + "name": "unit.volt", + "symbol": "V", + "tags": ["electric potential","electric tension","voltage","volt","volts","V","power source","battery","battery level"] +}, +{ + "name": "unit.kilovolts", + "symbol": "kV", + "tags": ["electric potential","electric tension","voltage","kilovolt","kilovolts","kV"] +}, +{ + "name": "unit.dbmV", + "symbol": "dBmV", + "tags": ["decibels millivolt","voltage level","signal","dBmV"] +}, +{ + "name": "unit.volt-meter", + "symbol": "V·m", + "tags": ["electric flux","volt-meter","V·m"] +}, +{ + "name": "unit.kilovolt-meter", + "symbol": "kV·m", + "tags": ["electric flux","kilovolt-meter","kV·m"] +}, +{ + "name": "unit.megavolt-meter", + "symbol": "MV·m", + "tags": ["electric flux","megavolt-meter","MV·m"] +}, +{ + "name": "unit.microvolt-meter", + "symbol": "µV·m", + "tags": ["electric flux","microvolt-meter","µV·m"] +}, +{ + "name": "unit.millivolt-meter", + "symbol": "mV·m", + "tags": ["electric flux","millivolt-meter","mV·m"] +}, +{ + "name": "unit.nanovolt-meter", + "symbol": "nV·m", + "tags": ["electric flux","nanovolt-meter","nV·m"] +}, +{ + "name": "unit.ohm", + "symbol": "Ω", + "tags": ["electrical resistance","resistance","impedance","ohm"] +}, +{ + "name": "unit.microohm", + "symbol": "μΩ", + "tags": ["electrical resistance","resistance","microohm","μΩ"] +}, +{ + "name": "unit.milliohm", + "symbol": "mΩ", + "tags": ["electrical resistance","resistance","milliohm","mΩ"] +}, +{ + "name": "unit.kilohm", + "symbol": "kΩ", + "tags": ["electrical resistance","resistance","kilohm","kΩ"] +}, +{ + "name": "unit.megohm", + "symbol": "MΩ", + "tags": ["electrical resistance","resistance","megohm","MΩ"] +}, +{ + "name": "unit.gigohm", + "symbol": "GΩ", + "tags": ["electrical resistance","resistance","gigohm","GΩ"] +}, +{ + "name": "unit.hertz", + "symbol": "Hz", + "tags": ["frequency","cycles per second","hertz","Hz"] +}, +{ + "name": "unit.kilohertz", + "symbol": "kHz", + "tags": ["frequency","cycles per second","kilohertz","kHz"] +}, +{ + "name": "unit.megahertz", + "symbol": "MHz", + "tags": ["frequency","cycles per second","megahertz","MHz"] +}, +{ + "name": "unit.gigahertz", + "symbol": "GHz", + "tags": ["frequency","cycles per second","gigahertz","GHz"] +}, +{ + "name": "unit.rpm", + "symbol": "RPM", + "tags": ["speed","velocity","cycle","engine","Revolutions Per Minute","RPM","angular velocity","rotation speed"] +}, +{ + "name": "unit.candela-per-square-meter", + "symbol": "cd/m²", + "tags": ["brightness","light level","Luminance","Candela per square meter","cd/m²"] +}, +{ + "name": "unit.candela", + "symbol": "cd", + "tags": ["light intensity","candle power","luminous intensity","Candela","cd"] +}, +{ + "name": "unit.lumen", + "symbol": "lm", + "tags": ["total light output","light power","luminous flux","Lumen","lm"] +}, +{ + "name": "unit.lux", + "symbol": "lx", + "tags": ["illumination","light level on a surface","illuminance","Lux","lx"] +}, +{ + "name": "unit.foot-candle", + "symbol": "fc", + "tags": ["illuminance","light level","foot-candle","fc"] +}, +{ + "name": "unit.lumen-per-square-meter", + "symbol": "lm/m²", + "tags": ["illuminance","light level","lumen per square meter","lm/m²"] +}, +{ + "name": "unit.lux-second", + "symbol": "lx·s", + "tags": ["light exposure","illumination time","light dosage","Lux second","lx·s"] +}, +{ + "name": "unit.lumen-second", + "symbol": "lm·s", + "tags": ["total light energy","luminous energy","Lumen second","lm·s"] +}, +{ + "name": "unit.lumens-per-watt", + "symbol": "lm/W", + "tags": ["lighting efficiency","light output per energy","luminous efficacy","Lumens per watt","lm/W"] +}, +{ + "name": "unit.absorbance", + "symbol": "AU", + "tags": ["optical density","light absorption","absorbance","AU"] +}, +{ + "name": "unit.mole", + "symbol": "mol", + "tags": ["amount of substance","substance quantity","mole","moles","mol"] +}, +{ + "name": "unit.nanomole", + "symbol": "nmol", + "tags": ["amount of substance","substance quantity","concentration","nanomole","nmol"] +}, +{ + "name": "unit.micromole", + "symbol": "μmol", + "tags": ["amount of substance","substance quantity","micromole","μmol"] +}, +{ + "name": "unit.millimole", + "symbol": "mmol", + "tags": ["amount of substance","substance quantity","millimole","mmol"] +}, +{ + "name": "unit.kilomole", + "symbol": "kmol", + "tags": ["amount of substance","substance quantity","kilomole","kmol"] +}, +{ + "name": "unit.mole-per-cubic-meter", + "symbol": "mol/m³", + "tags": ["concentration","amount of substance","mole per cubic meter","mol/m³"] +}, +{ + "name": "unit.percent", + "symbol": "%", + "tags": ["power source","state of charge (SoC)","battery","battery level","level","humidity","moisture","percentage", + "relative humidity","water content","soil moisture","irrigation","water in soil","soil water content","VWC", + "Volumetric Water Content","Total Harmonic Distortion","THD","power quality","UV Transmittance","%"] +}, +{ + "name": "unit.rssi", + "symbol": "rssi", + "tags": ["signal strength","signal level","received signal strength indicator","rssi","dBm"] +}, +{ + "name": "unit.ppm", + "symbol": "ppm", + "tags": ["carbon dioxide","co²","carbon monoxide","co","aqi","air quality","total volatile organic compounds","tvoc","ppm"] +}, +{ + "name": "unit.ppb", + "symbol": "ppb", + "tags": ["ozone","o³","nitrogen dioxide","no²","sulfur dioxide","so²","aqi","air quality","tvoc","ppb"] +}, +{ + "name": "unit.micrograms-per-cubic-meter", + "symbol": "µg/m³", + "tags": ["coarse particulate matter","pm10","fine particulate matter","pm2.5","aqi","air quality", + "total volatile organic compounds","tvoc","micrograms per cubic meter","µg/m³"] +}, +{ + "name": "unit.aqi", + "symbol": "aqi", + "tags": ["AQI","air quality index"] +}, +{ + "name": "unit.gram-per-cubic-meter", + "symbol": "g/m³", + "tags": ["humidity","moisture","absolute humidity","g/m³"] +}, +{ + "name": "unit.gram-per-kilogram", + "symbol": "g/kg", + "tags": ["humidity","moisture","specific humidity","g/kg"] +}, +{ + "name": "unit.millimeters-per-second", + "symbol": "mm/s", + "tags": ["velocity","speed","rate of motion","peak","peak to peak","root mean square (RMS)","vibration","mm/s"] +}, +{ + "name": "unit.neper", + "symbol": "Np", + "tags": ["logarithmic unit","ratio","gain","loss","attenuation","neper","Np"] +}, +{ + "name": "unit.bel", + "symbol": "B", + "tags": ["logarithmic unit","power ratio","intensity ratio","bel","B"] +}, +{ + "name": "unit.decibel", + "symbol": "dB", + "tags": ["noise level","sound level","volume","acoustics","decibel","dB"] +}, +{ + "name": "unit.meters-per-second-squared", + "symbol": "m/s²", + "tags": ["peak","peak to peak","root mean square (RMS)","vibration","meters per second squared","m/s²"] +}, +{ + "name": "unit.becquerel", + "symbol": "Bq", + "tags": ["radioactivity","radiation","becquerel","Bq"] +}, +{ + "name": "unit.curie", + "symbol": "Ci", + "tags": ["radioactivity","radiation","curie","Ci"] +}, +{ + "name": "unit.gray", + "symbol": "Gy", + "tags": ["radiation dose","gray","Gy"] +}, +{ + "name": "unit.sievert", + "symbol": "Sv", + "tags": ["radiation dose","sievert","radiation dose equivalent2","Sv"] +}, +{ + "name": "unit.roentgen", + "symbol": "R", + "tags": ["radiation exposure","roentgen","R"] +}, +{ + "name": "unit.cps", + "symbol": "cps", + "tags": ["radiation detection","counts per second","cps"] +}, +{ + "name": "unit.rad", + "symbol": "Rad", + "tags": ["radiation dose","rad"] +}, +{ + "name": "unit.rem", + "symbol": "Rem", + "tags": ["radiation dose equivalent","rem"] +}, +{ + "name": "unit.dps", + "symbol": "dps", + "tags": ["radioactive decay","radioactivity","disintegrations per second","dps"] +}, +{ + "name": "unit.rutherford", + "symbol": "Rd", + "tags": ["radioactive decay","radioactivity","rutherford","Rd"] +}, +{ + "name": "unit.coulombs-per-kilogram", + "symbol": "C/kg", + "tags": ["radiation exposure","dose","coulombs per kilogram","electric charge-to-mass ratio","C/kg"] +}, +{ + "name": "unit.becquerels-per-cubic-meter", + "symbol": "Bq/m³", + "tags": ["radioactivity","radiation","becquerels per cubic meter","Bq/m³"] +}, +{ + "name": "unit.curies-per-liter", + "symbol": "Ci/L", + "tags": ["radioactivity","radiation","curies per liter","Ci/L"] +}, +{ + "name": "unit.becquerels-per-second", + "symbol": "Bq/s", + "tags": ["radioactive decay rate","becquerels per second","Bq/s"] +}, +{ + "name": "unit.curies-per-second", + "symbol": "Ci/s", + "tags": ["radioactive decay rate","curies per second","Ci/s"] +}, +{ + "name": "unit.gy-per-second", + "symbol": "Gy/s", + "tags": ["absorbed dose rate","radiation dose rate","gray per second","Gy/s"] +}, +{ + "name": "unit.watt-per-steradian", + "symbol": "W/sr", + "tags": ["radiant intensity","power per unit solid angle","watt per steradian","W/sr"] +}, +{ + "name": "unit.watt-per-square-metre-steradian", + "symbol": "W/(m²·sr)", + "tags": ["radiance","radiant flux density","watt per square metre-steradian","W/(m²·sr)"] +}, +{ + "name": "unit.ph-level", + "symbol": "pH", + "tags": ["acidity","alkalinity","neutral","acid","base","pH","soil pH","water quality","water pH"] +}, +{ + "name": "unit.turbidity", + "symbol": "NTU", + "tags": ["water turbidity","water clarity","Nephelometric Turbidity Units","NTU"] +}, +{ + "name": "unit.mg-per-liter", + "symbol": "mg/L", + "tags": ["dissolved oxygen","water quality","mg/L"] +}, +{ + "name": "unit.microsiemens-per-centimeter", + "symbol": "µS/cm", + "tags": ["Electrical conductivity","water quality","soil quality","microsiemens per centimeter","µS/cm"] +}, +{ + "name": "unit.millisiemens-per-meter", + "symbol": "mS/m", + "tags": ["Electrical conductivity","water quality","soil quality","millisiemens per meter","mS/m"] +}, +{ + "name": "unit.siemens-per-meter", + "symbol": "S/m", + "tags": ["Electrical conductivity","water quality","soil quality","siemens per meter","S/m"] +}, +{ + "name": "unit.kilogram-per-cubic-meter", + "symbol": "kg/m³", + "tags": ["density","mass per unit volume","kg/m³"] +}, +{ + "name": "unit.gram-per-cubic-centimeter", + "symbol": "g/cm³", + "tags": ["density","mass per unit volume","g/cm³"] +}, +{ + "name": "unit.kilogram-per-square-meter", + "symbol": "kg/m²", + "tags": ["density","surface density","areal density","mass per unit area","kg/m²"] +}, +{ + "name": "unit.milligram-per-milliliter", + "symbol": "mg/mL", + "tags": ["concentration","mass per volume","mg/mL"] +}, +{ + "name": "unit.pound-per-cubic-foot", + "symbol": "lb/ft³", + "tags": ["Density","mass per unit volume","lb/ft³"] +}, +{ + "name": "unit.ounces-per-cubic-inch", + "symbol": "oz/in³", + "tags": ["density","mass per unit volume","oz/in³"] +}, +{ + "name": "unit.tons-per-cubic-yard", + "symbol": "ton/yd³", + "tags": ["density","mass per unit volume","ton/yd³"] +}, +{ + "name": "unit.particle-density", + "symbol": "particles/mL", + "tags": ["particle concentration","count","particles/mL"] +}, +{ + "name": "unit.kilometers-per-liter", + "symbol": "km/L", + "tags": ["fuel efficiency","km/L"] +}, +{ + "name": "unit.miles-per-gallon", + "symbol": "mpg", + "tags": ["fuel efficiency","mpg"] +}, +{ + "name": "unit.liters-per-100-km", + "symbol": "L/100km", + "tags": ["fuel efficiency","L/100km"] +}, +{ + "name": "unit.gallons-per-mile", + "symbol": "gal/mi", + "tags": ["fuel efficiency","gal/mi"] +}, +{ + "name": "unit.liters-per-hour", + "symbol": "L/hr", + "tags": ["fuel consumption","L/hr"] +}, +{ + "name": "unit.gallons-per-hour", + "symbol": "gal/hr", + "tags": ["fuel consumption","gal/hr"] +}, +{ + "name": "unit.beats-per-minute", + "symbol": "bpm", + "tags": ["heart rate","pulse","bpm"] +}, +{ + "name": "unit.millimeters-of-mercury", + "symbol": "mmHg", + "tags": ["blood pressure","systolic","diastolic","mmHg"] +}, +{ + "name": "unit.milligrams-per-deciliter", + "symbol": "mg/dL", + "tags": ["glucose","blood sugar","glucose level","mg/dL"] +}, +{ + "name": "unit.g-force", + "symbol": "G", + "tags": ["acceleration","gravity","force","g-load","G"] +}, +{ + "name": "unit.kilonewton", + "symbol": "kN", + "tags": ["force","kN"] +}, +{ + "name": "unit.kilogram-force", + "symbol": "kgf", + "tags": ["force","kgf"] +}, +{ + "name": "unit.pound-force", + "symbol": "lbf", + "tags": ["force","lbf"] +}, +{ + "name": "unit.kilopound-force", + "symbol": "klbf", + "tags": ["force","klbf"] +}, +{ + "name": "unit.dyne", + "symbol": "dyn", + "tags": ["force","dyn"] +}, +{ + "name": "unit.poundal", + "symbol": "pdl", + "tags": ["force","pdl"] +}, +{ + "name": "unit.kip", + "symbol": "kip", + "tags": ["force","kip"] +}, +{ + "name": "unit.gal", + "symbol": "Gal", + "tags": ["acceleration","gravity","g-force","Gal"] +}, +{ + "name": "unit.gravity", + "symbol": "gravity", + "tags": ["acceleration","gravity","g-force"] +}, +{ + "name": "unit.hectopascal", + "symbol": "hPa", + "tags": ["atmospheric pressure","air pressure","weather","altitude","flight","hPa"] +}, +{ + "name": "unit.atmosphere", + "symbol": "atm", + "tags": ["atmospheric pressure","air pressure","weather","altitude","flight","atm"] +}, +{ + "name": "unit.millibars", + "symbol": "mb", + "tags": ["atmospheric pressure","air pressure","weather","altitude","flight","mb"] +}, +{ + "name": "unit.inch-of-mercury", + "symbol": "inHg", + "tags": ["atmospheric pressure","air pressure","weather","altitude","flight","inHg","richter"] +}, +{ + "name": "unit.richter-scale", + "symbol": "richter", + "tags": ["earthquake","seismic activity","richter"] +}, +{ + "name": "unit.second", + "symbol": "s", + "tags": ["time","duration","interval","angle","second","arcsecond","sec"] +}, +{ + "name": "unit.minute", + "symbol": "min", + "tags": ["time","duration","interval","angle","minute","arcminute","min"] +}, +{ + "name": "unit.hour", + "symbol": "h", + "tags": ["time","duration","interval","h"] +}, +{ + "name": "unit.day", + "symbol": "d", + "tags": ["time","duration","interval","d"] +}, +{ + "name": "unit.week", + "symbol": "wk", + "tags": ["time","duration","interval","wk"] +}, +{ + "name": "unit.month", + "symbol": "mo", + "tags": ["time","duration","interval","mo"] +}, +{ + "name": "unit.year", + "symbol": "yr", + "tags": ["time","duration","interval","yr"] +}, +{ + "name": "unit.cubic-foot-per-minute", + "symbol": "ft³/min", + "tags": ["airflow","ventilation","HVAC","gas flow rate","CFM","flow rate","fluid flow","cubic foot per minute","ft³/min"] +}, +{ + "name": "unit.cubic-meters-per-hour", + "symbol": "m³/hr", + "tags": ["airflow","ventilation","HVAC","gas flow rate","cubic meters per hour","m³/hr"] +}, +{ + "name": "unit.cubic-meters-per-second", + "symbol": "m³/s", + "tags": ["airflow","ventilation","HVAC","gas flow rate","cubic meters per second","m³/s"] +}, +{ + "name": "unit.liter-per-second", + "symbol": "L/s", + "tags": ["airflow","ventilation","HVAC","gas flow rate","liter per second","L/s"] +}, +{ + "name": "unit.liter-per-minute", + "symbol": "L/min", + "tags": ["airflow","ventilation","HVAC","gas flow rate","liter per minute","L/min"] +}, +{ + "name": "unit.gallons-per-minute", + "symbol": "GPM", + "tags": ["airflow","ventilation","HVAC","gas flow rate","gallons per minute","GPM"] +}, +{ + "name": "unit.cubic-foot-per-second", + "symbol": "ft³/s", + "tags": ["flow rate","fluid flow","cubic foot per second","cubic feet per second","ft³/s"] +}, +{ + "name": "unit.milliliters-per-minute", + "symbol": "mL/min", + "tags": ["Flow rate","fluid dynamics","milliliters per minute","mL/min"] +}, +{ + "name": "unit.bit", + "symbol": "bit", + "tags": ["data","binary digit","information","bit"] +}, +{ + "name": "unit.byte", + "symbol": "B", + "tags": ["data","byte","information","storage","memory","B"] +}, +{ + "name": "unit.kilobyte", + "symbol": "KB", + "tags": ["data","kilobyte","KB"] +}, +{ + "name": "unit.megabyte", + "symbol": "MB", + "tags": ["data","megabyte","MB"] +}, +{ + "name": "unit.gigabyte", + "symbol": "GB", + "tags": ["data","gigabyte","GB"] +}, +{ + "name": "unit.terabyte", + "symbol": "TB", + "tags": ["data","terabyte","TB"] +}, +{ + "name": "unit.petabyte", + "symbol": "PB", + "tags": ["data","petabyte","PB"] +}, +{ + "name": "unit.exabyte", + "symbol": "EB", + "tags": ["data","exabyte","EB"] +}, +{ + "name": "unit.zettabyte", + "symbol": "ZB", + "tags": ["data","zettabyte","ZB"] +}, +{ + "name": "unit.yottabyte", + "symbol": "YB", + "tags": ["data","yottabyte","YB"] +}, +{ + "name": "unit.bit-per-second", + "symbol": "bps", + "tags": ["data transfer rate","bps"] +}, +{ + "name": "unit.kilobit-per-second", + "symbol": "kbps", + "tags": ["data transfer rate","kbps"] +}, +{ + "name": "unit.megabit-per-second", + "symbol": "Mbps", + "tags": ["data transfer rate","Mbps"] +}, +{ + "name": "unit.gigabit-per-second", + "symbol": "Gbps", + "tags": ["data transfer rate","Gbps"] +}, +{ + "name": "unit.terabit-per-second", + "symbol": "Tbps", + "tags": ["data transfer rate","Tbps"] +}, +{ + "name": "unit.byte-per-second", + "symbol": "B/s", + "tags": ["data transfer rate","B/s"] +}, +{ + "name": "unit.kilobyte-per-second", + "symbol": "KB/s", + "tags": ["data transfer rate","KB/s"] +}, +{ + "name": "unit.megabyte-per-second", + "symbol": "MB/s", + "tags": ["data transfer rate","MB/s"] +}, +{ + "name": "unit.gigabyte-per-second", + "symbol": "GB/s", + "tags": ["data transfer rate","GB/s"] +}, +{ + "name": "unit.degree", + "symbol": "deg", + "tags": ["angle","degree","degrees","deg"] +}, +{ + "name": "unit.radian", + "symbol": "rad", + "tags": ["angle","radian","radians","rad"] +}, +{ + "name": "unit.gradian", + "symbol": "grad", + "tags": ["angle","gradian","grades","grad"] +}, +{ + "name": "unit.mil", + "symbol": "mil", + "tags": ["angle","military angle","angular mil","mil"] +}, +{ + "name": "unit.revolution", + "symbol": "rev", + "tags": ["angle","revolution","full circle","complete turn","rev"] +}, +{ + "name": "unit.siemens", + "symbol": "S", + "tags": ["electrical conductance","conductance","siemens","S"] +}, +{ + "name": "unit.millisiemens", + "symbol": "mS", + "tags": ["electrical conductance","conductance","millisiemens","mS"] +}, +{ + "name": "unit.microsiemens", + "symbol": "μS", + "tags": ["electrical conductance","conductance","microsiemens","μS"] +}, +{ + "name": "unit.kilosiemens", + "symbol": "kS", + "tags": ["electrical conductance","conductance","kilosiemens","kS"] +}, +{ + "name": "unit.megasiemens", + "symbol": "MS", + "tags": ["electrical conductance","conductance","megasiemens","MS"] +}, +{ + "name": "unit.gigasiemens", + "symbol": "GS", + "tags": ["electrical conductance","conductance","gigasiemens","GS"] +}, +{ + "name": "unit.farad", + "symbol": "F", + "tags": ["electric capacitance","capacitance","farad","F"] +}, +{ + "name": "unit.millifarad", + "symbol": "mF", + "tags": ["electric capacitance","capacitance","millifarad","mF"] +}, +{ + "name": "unit.microfarad", + "symbol": "μF", + "tags": ["electric capacitance","capacitance","microfarad","μF"] +}, +{ + "name": "unit.nanofarad", + "symbol": "nF", + "tags": ["electric capacitance","capacitance","nanofarad","nF"] +}, +{ + "name": "unit.picofarad", + "symbol": "pF", + "tags": ["electric capacitance","capacitance","picofarad","pF"] +}, +{ + "name": "unit.kilofarad", + "symbol": "kF", + "tags": ["electric capacitance","capacitance","kilofarad","kF"] +}, +{ + "name": "unit.megafarad", + "symbol": "MF", + "tags": ["electric capacitance","capacitance","megafarad","MF"] +}, +{ + "name": "unit.gigafarad", + "symbol": "GF", + "tags": ["electric capacitance","capacitance","gigafarad","GF"] +}, +{ + "name": "unit.terfarad", + "symbol": "TF", + "tags": ["electric capacitance","capacitance","terafarad","TF"] +}, +{ + "name": "unit.farad-per-meter", + "symbol": "F/m", + "tags": ["electric permittivity","farad per meter","F/m"] +}, +{ + "name": "unit.tesla", + "symbol": "T", + "tags": ["magnetic field","magnetic field strength","tesla","T","magnetic flux density"] +}, +{ + "name": "unit.gauss", + "symbol": "G", + "tags": ["magnetic field","magnetic field strength","gauss","G","magnetic flux density"] +}, +{ + "name": "unit.kilogauss", + "symbol": "kG", + "tags": ["magnetic field","magnetic field strength","kilogauss","kG","magnetic flux density"] +}, +{ + "name": "unit.millitesla", + "symbol": "mT", + "tags": ["magnetic field","magnetic field strength","millitesla","mT"] +}, +{ + "name": "unit.microtesla", + "symbol": "μT", + "tags": ["magnetic field","magnetic field strength","microtesla","μT"] +}, +{ + "name": "unit.nanotesla", + "symbol": "nT", + "tags": ["magnetic field","magnetic field strength","nanotesla","nT"] +}, +{ + "name": "unit.kilotesla", + "symbol": "kT", + "tags": ["magnetic field","magnetic field strength","kilotesla","kT"] +}, +{ + "name": "unit.megatesla", + "symbol": "MT", + "tags": ["magnetic field","magnetic field strength","megatesla","MT"] +}, +{ + "name": "unit.millitesla-square-meters", + "symbol": "millitesla square meters", + "tags": ["magnetic field","millitesla square meters"] +}, +{ + "name": "unit.gamma", + "symbol": "γ", + "tags": ["magnetic flux density","gamma","γ"] +}, +{ + "name": "unit.lambda", + "symbol": "λ", + "tags": ["wavelength","lambda","λ"] +}, +{ + "name": "unit.square-meter-per-second", + "symbol": "m²/s", + "tags": ["kinematic viscosity","m²/s"] +}, +{ + "name": "unit.square-centimeter-per-second", + "symbol": "cm²/s", + "tags": ["kinematic viscosity","cm²/s"] +}, +{ + "name": "unit.stoke", + "symbol": "St", + "tags": ["kinematic viscosity","stokes","St"] +}, +{ + "name": "unit.centistokes", + "symbol": "cSt", + "tags": ["kinematic viscosity","centistokes","cSt"] +}, +{ + "name": "unit.square-foot-per-second", + "symbol": "ft²/s", + "tags": ["kinematic viscosity","ft²/s"] +}, +{ + "name": "unit.square-inch-per-second", + "symbol": "in²/s", + "tags": ["kinematic viscosity","in²/s"] +}, +{ + "name": "unit.pascal-second", + "symbol": "Pa·s", + "tags": ["dynamic viscosity","viscosity","fluid mechanics","pascal-second","Pa·s"] +}, +{ + "name": "unit.centipoise", + "symbol": "cP", + "tags": ["viscosity","dynamic viscosity","fluid viscosity","centipoise","cP"] +}, +{ + "name": "unit.poise", + "symbol": "P", + "tags": ["viscosity","dynamic viscosity","fluid viscosity","poise","P"] +}, +{ + "name": "unit.reynolds", + "symbol": "Re", + "tags": ["fluid flow regime","fluid mechanics","reynolds","Re"] +}, +{ + "name": "unit.pound-per-foot-hour", + "symbol": "lb/(ft·h)", + "tags": ["pound per foot-hour","lb/(ft·h)"] +}, +{ + "name": "unit.newton-second-per-square-meter", + "symbol": "N·s/m²", + "tags": ["newton second per square meter","N·s/m²"] +}, +{ + "name": "unit.dyne-second-per-square-centimeter", + "symbol": "dyn·s/cm²", + "tags": ["dyne second per square centimeter","dyn·s/cm²"] +}, +{ + "name": "unit.kilogram-per-meter-second", + "symbol": "kg/(m·s)", + "tags": ["kilogram per meter-second","kg/(m·s)"] +}, +{ + "name": "unit.tesla-square-meters", + "symbol": "T/m²", + "tags": ["magnetic flux density","tesla square meters","T/m²"] +}, +{ + "name": "unit.maxwell", + "symbol": "Mx", + "tags": ["magnetic flux","magnetic field","maxwell","Mx"] +}, +{ + "name": "unit.tesla-per-meter", + "symbol": "T/m", + "tags": ["magnetic field","tesla per meter","T/m"] +}, +{ + "name": "unit.gauss-per-centimeter", + "symbol": "G/cm", + "tags": ["magnetic field","gauss per centimeter","G/cm"] +}, +{ + "name": "unit.weber", + "symbol": "Wb", + "tags": ["magnetic flux","weber","Wb"] +}, +{ + "name": "unit.microweber", + "symbol": "µWb", + "tags": ["magnetic flux","microweber","µWb"] +}, +{ + "name": "unit.milliweber", + "symbol": "mWb", + "tags": ["magnetic flux","milliweber","mWb"] +}, +{ + "name": "unit.gauss-square-centimeter", + "symbol": "G·cm²", + "tags": ["magnetic flux","gauss-square centimeter","G·cm²"] +}, +{ + "name": "unit.kilogauss-square-centimeter", + "symbol": "kG·cm²", + "tags": ["magnetic flux","kilogauss-square centimeter","kG·cm²"] +}, +{ + "name": "unit.henry", + "symbol": "H", + "tags": ["inductance","magnetic induction","H"] +}, +{ + "name": "unit.millihenry", + "symbol": "mH", + "tags": ["inductance","millihenry","mH"] +}, +{ + "name": "unit.microhenry", + "symbol": "µH", + "tags": ["inductance","microhenry","µH"] +}, +{ + "name": "unit.nanohenry", + "symbol": "nH", + "tags": ["inductance","nanohenry","nH"] +}, +{ + "name": "unit.henry-per-meter", + "symbol": "H/m", + "tags": ["magnetic permeability","henry per meter","H/m"] +}, +{ + "name": "unit.tesla-meter-per-ampere", + "symbol": "T·m/A", + "tags": ["magnetic field","Tesla Meter per Ampere","T·m/A","magnetic flux"] +}, +{ + "name": "unit.gauss-per-oersted", + "symbol": "G/Oe", + "tags": ["magnetic field","Gauss per Oersted","G/Oe"] +}, +{ + "name": "unit.kilogram-per-mole", + "symbol": "kg/mol", + "tags": ["molar mass","kilogram per mole","kg/mol"] +}, +{ + "name": "unit.gram-per-mole", + "symbol": "g/mol", + "tags": ["molar mass","gram per mole","g/mol"] +}, +{ + "name": "unit.milligram-per-mole", + "symbol": "mg/mol", + "tags": ["molar mass","milligram per mole","mg/mol"] +}, +{ + "name": "unit.joule-per-mole", + "symbol": "J/mol", + "tags": ["molar energy","joule per mole","J/mol"] +}, +{ + "name": "unit.joule-per-mole-kelvin", + "symbol": "J/(mol·K)", + "tags": ["molar heat capacity","joule per mole-kelvin","J/(mol·K)"] +}, +{ + "name": "unit.millivolts-per-meter", + "symbol": "mV/m", + "tags": ["electric field strength","millivolts per meter","mV/m"] +}, +{ + "name": "unit.volts-per-meter", + "symbol": "V/m", + "tags": ["electric field strength","volts per meter","V/m"] +}, +{ + "name": "unit.kilovolts-per-meter", + "symbol": "kV/m", + "tags": ["electric field strength","kilovolts per meter","kV/m"] +}, +{ + "name": "unit.radian-per-second", + "symbol": "rad/s", + "tags": ["angular velocity","rotation speed","rad/s"] +}, +{ + "name": "unit.radian-per-second-squared", + "symbol": "rad/s²", + "tags": ["angular acceleration","rotation rate of change","rad/s²"] +}, +{ + "name": "unit.revolutions-per-minute-per-second", + "symbol": "rpm/s", + "tags": ["angular acceleration","rotation rate of change","rpm/s"] +}, +{ + "name": "unit.revolutions-per-minute-per-second-squared", + "symbol": "rpm/s²", + "tags": ["angular acceleration","rotation rate of change","rpm/s²"] +}, +{ + "name": "unit.deg-per-second", + "symbol": "deg/s", + "tags": ["angular velocity","degrees per second","deg/s"] +}, +{ + "name": "unit.degrees-brix", + "symbol": "°Bx", + "tags": ["sugar content","fruit ripeness","Bx"] +}, +{ + "name": "unit.katal", + "symbol": "kat", + "tags": ["catalytic activity","enzyme activity","kat"] +}, +{ + "name": "unit.katal-per-cubic-metre", + "symbol": "kat/m³", + "tags": ["catalytic activity concentration","enzyme concentration","kat/m³"] +}] From 5e0a6667f51c726b20df38654d915d4999facdba Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 20 Jul 2023 12:30:50 +0300 Subject: [PATCH 095/168] EntitiesTableWidgetComponent rename rowPointer to hasRowAction --- .../widget/lib/entities-table-widget.component.html | 2 +- .../components/widget/lib/entities-table-widget.component.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html index 1844ab3ea5..4f941a0047 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html @@ -89,7 +89,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts index 4a5a891703..b5e8d90b51 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts @@ -150,7 +150,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni public displayedColumns: string[] = []; public entityDatasource: EntityDatasource; public noDataDisplayMessageText: string; - public rowPointer: boolean; + public hasRowAction: boolean; private setCellButtonAction: boolean; private cellContentCache: Array = []; @@ -279,7 +279,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni this.setCellButtonAction = !!this.ctx.actionsApi.getActionDescriptors('actionCellButton').length; - this.rowPointer = !!this.ctx.actionsApi.getActionDescriptors('rowClick').length || !!this.ctx.actionsApi.getActionDescriptors('rowDoubleClick').length; + this.hasRowAction = !!this.ctx.actionsApi.getActionDescriptors('rowClick').length || !!this.ctx.actionsApi.getActionDescriptors('rowDoubleClick').length; if (this.settings.entitiesTitle && this.settings.entitiesTitle.length) { this.entitiesTitlePattern = this.utils.customTranslation(this.settings.entitiesTitle, this.settings.entitiesTitle); From db46b7988da7cce2f75d4d1e4c18372f6c2cb3e7 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 20 Jul 2023 12:33:15 +0300 Subject: [PATCH 096/168] refactored code to take into account operating system --- .../server/controller/BaseController.java | 9 + .../controller/ControllerConstants.java | 2 + .../DeviceConnectivityController.java | 108 +++++ .../server/controller/DeviceController.java | 33 -- .../src/main/resources/thingsboard.yml | 2 +- .../DeviceConnectivityControllerTest.java | 398 ++++++++++++++++++ .../controller/DeviceControllerTest.java | 184 +------- .../dao/device/DeviceConnectivityService.java | 13 +- .../server/dao/device/DeviceService.java | 3 - .../dao/device/DeviceConnectivityInfo.java | 2 +- .../DeviceConnectivityMqttSslCertService.java | 53 --- .../server/dao/device/DeviceServiceImpl.java | 109 ----- .../DeviceСonnectivityServiceImpl.java | 224 ++++++++++ .../dao/util/DeviceConnectivityUtil.java | 51 ++- 14 files changed, 802 insertions(+), 389 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java rename dao/src/main/java/org/thingsboard/server/dao/device/TbDeviceConnectivitySslCertService.java => common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java (62%) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 68a987a0bc..a03fcd36a4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -113,6 +113,7 @@ import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.ClaimDevicesService; +import org.thingsboard.server.dao.device.DeviceConnectivityService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; @@ -163,6 +164,7 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.mail.MessagingException; import javax.servlet.http.HttpServletResponse; import javax.validation.ConstraintViolation; +import java.io.IOException; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -208,6 +210,9 @@ public abstract class BaseController { @Autowired protected DeviceService deviceService; + @Autowired + protected DeviceConnectivityService deviceConnectivityService; + @Autowired protected DeviceProfileService deviceProfileService; @@ -755,6 +760,10 @@ public abstract class BaseController { return checkEntityId(resourceId, resourceService::findResourceInfoById, operation); } + String checkSslServerPemFile(String protocol) throws ThingsboardException, IOException { + return checkNotNull(deviceConnectivityService.getSslServerChain(protocol), "Mqtt ssl server chain pem file is not found"); + } + OtaPackage checkOtaPackageId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException { return checkEntityId(otaPackageId, otaPackageService::findOtaPackageById, operation); } diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index a6a49f6b3c..f31cebd258 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -24,6 +24,7 @@ public class ControllerConstants { protected static final String CUSTOMER_ID = "customerId"; protected static final String TENANT_ID = "tenantId"; protected static final String DEVICE_ID = "deviceId"; + protected static final String PROTOCOL = "protocol"; protected static final String EDGE_ID = "edgeId"; protected static final String RPC_ID = "rpcId"; protected static final String ENTITY_ID = "entityId"; @@ -34,6 +35,7 @@ public class ControllerConstants { protected static final String DASHBOARD_ID_PARAM_DESCRIPTION = "A string value representing the dashboard id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String RPC_ID_PARAM_DESCRIPTION = "A string value representing the rpc id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String DEVICE_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; + protected static final String PROTOCOL_PARAM_DESCRIPTION = "A string value representing the device connectivity protocol. Possible values: 'mqtt', 'mqtts', 'http', 'https', 'coap', 'coaps'"; protected static final String ENTITY_VIEW_ID_PARAM_DESCRIPTION = "A string value representing the entity view id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String DEVICE_PROFILE_ID_PARAM_DESCRIPTION = "A string value representing the device profile id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java new file mode 100644 index 0000000000..bf745a2033 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java @@ -0,0 +1,108 @@ +/** + * 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.controller; + +import com.fasterxml.jackson.databind.JsonNode; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.system.SystemSecurityService; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Map; + +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID; +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PROTOCOL; +import static org.thingsboard.server.controller.ControllerConstants.PROTOCOL_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT_SSL_PEM_FILE_NAME; + +@RestController +@TbCoreComponent +@RequestMapping("/api") +@RequiredArgsConstructor +@Slf4j +public class DeviceConnectivityController extends BaseController { + + private final SystemSecurityService systemSecurityService; + + @ApiOperation(value = "Get commands to publish device telemetry (getDevicePublishTelemetryCommands)", + notes = "Fetch the list of commands to publish device telemetry based on device profile " + + "If the user has the authority of 'Tenant Administrator', the server checks that the device is owned by the same tenant. " + + "If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK", + examples = @io.swagger.annotations.Example( + value = { + @io.swagger.annotations.ExampleProperty( + mediaType="application/json", + value="{\"http\":\"curl -v -X POST http://localhost:8080/api/v1/0ySs4FTOn5WU15XLmal8/telemetry --header Content-Type:application/json --data {temperature:25}\"," + + "\"mqtt\":\"mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -i myClient1 -u myUsername1 -P myPassword -m {temperature:25}\"," + + "\"coap\":\"coap-client -m POST coap://localhost:5683/api/v1/0ySs4FTOn5WU15XLmal8/telemetry -t json -e {temperature:25}\"}")}))}) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/device-connectivity/{deviceId}", method = RequestMethod.GET) + @ResponseBody + public JsonNode getDevicePublishTelemetryCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { + checkParameter(DEVICE_ID, strDeviceId); + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); + + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); + return deviceConnectivityService.findDevicePublishTelemetryCommands(baseUrl, device); + } + + @ApiOperation(value = "Download mqtt ssl certificate using file path defined in device.connectivity properties (downloadMqttServerCertificate)", notes = "Download Mqtt server certificate." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/device-connectivity/{protocol}/certificate/download", method = RequestMethod.GET) + @ResponseBody + public ResponseEntity downloadMqttServerCertificate(@ApiParam(value = PROTOCOL_PARAM_DESCRIPTION) + @PathVariable(PROTOCOL) String protocol) throws ThingsboardException, IOException { + String certificate = checkSslServerPemFile(protocol); + + ByteArrayResource cert = new ByteArrayResource(certificate.getBytes()); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + MQTT_SSL_PEM_FILE_NAME) + .header("x-filename", MQTT_SSL_PEM_FILE_NAME) + .contentLength(cert.contentLength()) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(cert); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index e080574d36..07adb1ef1c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -21,11 +21,8 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -79,12 +76,9 @@ import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import java.net.URISyntaxException; import javax.validation.Valid; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -173,33 +167,6 @@ public class DeviceController extends BaseController { return checkDeviceInfoId(deviceId, Operation.READ); } - @ApiOperation(value = "Get commands to publish device telemetry (getDevicePublishTelemetryCommands)", - notes = "Fetch the list of commands to publish device telemetry based on device profile " + - "If the user has the authority of 'Tenant Administrator', the server checks that the device is owned by the same tenant. " + - "If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + - TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", - examples = @io.swagger.annotations.Example( - value = { - @io.swagger.annotations.ExampleProperty( - mediaType="application/json", - value="{\"http\":\"curl -v -X POST http://localhost:8080/api/v1/0ySs4FTOn5WU15XLmal8/telemetry --header Content-Type:application/json --data {temperature:25}\"," + - "\"mqtt\":\"mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -i myClient1 -u myUsername1 -P myPassword -m {temperature:25}\"," + - "\"coap\":\"coap-client -m POST coap://localhost:5683/api/v1/0ySs4FTOn5WU15XLmal8/telemetry -t json -e {temperature:25}\"}")}))}) - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/device/{deviceId}/commands", method = RequestMethod.GET) - @ResponseBody - public Map getDevicePublishTelemetryCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) - @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { - checkParameter(DEVICE_ID, strDeviceId); - DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); - - String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); - return deviceService.findDevicePublishTelemetryCommands(baseUrl, device); - } - @ApiOperation(value = "Create Or Update Device (saveDevice)", notes = "Create or update the Device. When creating device, platform generates Device Id as " + UUID_WIKI_LINK + "Device credentials are also generated if not provided in the 'accessToken' request parameter. " + diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 6eb0a3948c..5886e74ce4 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1004,7 +1004,7 @@ device: enabled: "${DEVICE_CONNECTIVITY_MQTTS_ENABLED:false}" host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:}" port: "${DEVICE_CONNECTIVITY_MQTTS_PORT:8883}" - tb_server_chain_path: "${DEVICE_CONNECTIVITY_MQTTS_SERVER_CHAIN_PATH:}" + ssl_server_pem_path: "${DEVICE_CONNECTIVITY_MQTTS_SERVER_CHAIN_PATH:}" coap: enabled: "${DEVICE_CONNECTIVITY_COAP_ENABLED:true}" host: "${DEVICE_CONNECTIVITY_COAP_HOST:}" diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java new file mode 100644 index 0000000000..8e27857878 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -0,0 +1,398 @@ +/** + * 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.controller; + +import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.AdditionalAnswers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ThingsBoardExecutors; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; +import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; +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.device.credentials.BasicMqttCredentials; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceCredentialsId; +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.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; +import org.thingsboard.server.dao.device.DeviceDao; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.DOCKER; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.LINUX; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.WINDOWS; + +@TestPropertySource(properties = { + "device.connectivity.https.enabled=true", + "device.connectivity.mqtts.enabled=true", + "device.connectivity.coaps.enabled=true", +}) +@ContextConfiguration(classes = {DeviceConnectivityControllerTest.Config.class}) +@DaoSqlTest +public class DeviceConnectivityControllerTest extends AbstractControllerTest { + static final TypeReference> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() { + }; + + private static final String DEVICE_TELEMETRY_TOPIC = "v1/devices/customTopic"; + private static final String CHECK_DOCUMENTATION = "Check documentation"; + + ListeningExecutorService executor; + + private Tenant savedTenant; + private User tenantAdmin; + private DeviceProfileId mqttDeviceProfileId; + private DeviceProfileId coapDeviceProfileId; + + static class Config { + @Bean + @Primary + public DeviceDao deviceDao(DeviceDao deviceDao) { + return Mockito.mock(DeviceDao.class, AdditionalAnswers.delegatesTo(deviceDao)); + } + } + + @Before + public void beforeTest() throws Exception { + executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); + + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + DeviceProfile mqttProfile = new DeviceProfile(); + mqttProfile.setName("Mqtt device profile"); + mqttProfile.setType(DeviceProfileType.DEFAULT); + mqttProfile.setTransportType(DeviceTransportType.MQTT); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); + MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration(); + transportConfiguration.setDeviceTelemetryTopic(DEVICE_TELEMETRY_TOPIC); + deviceProfileData.setTransportConfiguration(transportConfiguration); + mqttProfile.setProfileData(deviceProfileData); + mqttProfile.setDefault(false); + mqttProfile.setDefaultRuleChainId(null); + + mqttDeviceProfileId = doPost("/api/deviceProfile", mqttProfile, DeviceProfile.class).getId(); + + DeviceProfile coapProfile = new DeviceProfile(); + coapProfile.setName("Coap device profile"); + coapProfile.setType(DeviceProfileType.DEFAULT); + coapProfile.setTransportType(DeviceTransportType.COAP); + DeviceProfileData deviceProfileData2 = new DeviceProfileData(); + deviceProfileData2.setConfiguration(new DefaultDeviceProfileConfiguration()); + deviceProfileData2.setTransportConfiguration(new CoapDeviceProfileTransportConfiguration()); + coapProfile.setProfileData(deviceProfileData); + coapProfile.setDefault(false); + coapProfile.setDefaultRuleChainId(null); + + coapDeviceProfileId = doPost("/api/deviceProfile", coapProfile, DeviceProfile.class).getId(); + } + + @After + public void afterTest() throws Exception { + executor.shutdownNow(); + + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant.getId().getId()) + .andExpect(status().isOk()); + } + + @Test + public void testFetchPublishTelemetryCommandsForDefaultDevice() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setType("default"); + Device savedDevice = doPost("/api/device", device, Device.class); + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + assertThat(commands).hasSize(3); + JsonNode httpCommands = commands.get(HTTP); + assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost:8080/api/v1/%s/telemetry " + + "--header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost:443/api/v1/%s/telemetry " + + "--header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + + + JsonNode linuxMqttCommands = commands.get(MQTT).get(LINUX); + assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + + "-u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(linuxMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + + "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + + JsonNode windowsMqttCommands = commands.get(MQTT).get(WINDOWS); + assertThat(windowsMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + + "-u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + + + JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + + " -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --volume pathToFile/tb-server-chain.pem:/tmp/tb-server-chain.pem " + + "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + + JsonNode linuxCoapCommands = commands.get(COAP).get(LINUX); + assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry " + + "-t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry" + + " -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + } + + @Test + public void testFetchPublishTelemetryCommandsForMqttDeviceWithAccessToken() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(mqttDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); + assertThat(commands).hasSize(1); + + JsonNode linuxMqttCommands = commands.get(MQTT).get(LINUX); + assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + + "-u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + assertThat(linuxMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + + "-t %s -u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + + JsonNode windowsMqttCommands = commands.get(MQTT).get(WINDOWS); + assertThat(windowsMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + + "-u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + + + JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + + " -p 1883 -t %s -u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --volume pathToFile/tb-server-chain.pem:/tmp/tb-server-chain.pem " + + "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + } + + @Test + public void testFetchPublishTelemetryCommandsForDeviceWithMqttBasicCreds() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(mqttDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + credentials.setCredentialsId(null); + credentials.setCredentialsType(DeviceCredentialsType.MQTT_BASIC); + BasicMqttCredentials basicMqttCredentials = new BasicMqttCredentials(); + String clientId = "testClientId"; + String userName = "testUsername"; + String password = "testPassword"; + basicMqttCredentials.setClientId(clientId); + basicMqttCredentials.setUserName(userName); + basicMqttCredentials.setPassword(password); + credentials.setCredentialsValue(JacksonUtil.toString(basicMqttCredentials)); + doPost("/api/device/credentials", credentials) + .andExpect(status().isOk()); + + + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); + assertThat(commands).hasSize(1); + + JsonNode linuxMqttCommands = commands.get(MQTT).get(LINUX); + assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + + "-i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + assertThat(linuxMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + + "-t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + + JsonNode windowsMqttCommands = commands.get(MQTT).get(WINDOWS); + assertThat(windowsMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + + "-i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + + + JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + + " -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --volume pathToFile/tb-server-chain.pem:/tmp/tb-server-chain.pem " + + "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + } + + @Test + public void testFetchPublishTelemetryCommandsForDeviceWithX509Creds() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(mqttDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + credentials.setCredentialsId(null); + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + credentials.setCredentialsValue("testValue"); + doPost("/api/device/credentials", credentials) + .andExpect(status().isOk()); + + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + assertThat(commands).hasSize(1); + assertThat(commands.get(MQTT).get(LINUX).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(MQTT).get(WINDOWS).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(MQTT).get(DOCKER).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); + } + + @Test + public void testFetchPublishTelemetryCommandsForСoapDevice() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(coapDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + assertThat(commands).hasSize(1); + + JsonNode linuxCommands = commands.get(COAP).get(LINUX); + assertThat(linuxCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(linuxCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + } + + @Test + public void testFetchPublishTelemetryCommandsForСoapDeviceWithX509Creds() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(coapDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + credentials.setCredentialsId(null); + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + credentials.setCredentialsValue("testValue"); + doPost("/api/device/credentials", credentials) + .andExpect(status().isOk()); + + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + assertThat(commands).hasSize(1); + assertThat(commands.get(COAP).get(LINUX).get(COAPS).asText()).isEqualTo(CHECK_DOCUMENTATION); + } +} diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 12fa4377f6..287e383317 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -93,27 +93,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; - -@TestPropertySource(properties = { - "device.connectivity.https.enabled=true", - "device.connectivity.mqtts.enabled=true", - "device.connectivity.coaps.enabled=true", -}) + @ContextConfiguration(classes = {DeviceControllerTest.Config.class}) @DaoSqlTest public class DeviceControllerTest extends AbstractControllerTest { static final TypeReference> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() { }; - private static final String DEVICE_TELEMETRY_TOPIC = "v1/devices/customTopic"; - private static final String CHECK_DOCUMENTATION = "Check documentation"; - ListeningExecutorService executor; List> futures; @@ -121,8 +107,6 @@ public class DeviceControllerTest extends AbstractControllerTest { private Tenant savedTenant; private User tenantAdmin; - private DeviceProfileId mqttDeviceProfileId; - private DeviceProfileId coapDeviceProfileId; @SpyBean private GatewayNotificationsService gatewayNotificationsService; @@ -157,34 +141,6 @@ public class DeviceControllerTest extends AbstractControllerTest { tenantAdmin.setLastName("Downs"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); - - DeviceProfile mqttProfile = new DeviceProfile(); - mqttProfile.setName("Mqtt device profile"); - mqttProfile.setType(DeviceProfileType.DEFAULT); - mqttProfile.setTransportType(DeviceTransportType.MQTT); - DeviceProfileData deviceProfileData = new DeviceProfileData(); - deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); - MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration(); - transportConfiguration.setDeviceTelemetryTopic(DEVICE_TELEMETRY_TOPIC); - deviceProfileData.setTransportConfiguration(transportConfiguration); - mqttProfile.setProfileData(deviceProfileData); - mqttProfile.setDefault(false); - mqttProfile.setDefaultRuleChainId(null); - - mqttDeviceProfileId = doPost("/api/deviceProfile", mqttProfile, DeviceProfile.class).getId(); - - DeviceProfile coapProfile = new DeviceProfile(); - coapProfile.setName("Coap device profile"); - coapProfile.setType(DeviceProfileType.DEFAULT); - coapProfile.setTransportType(DeviceTransportType.COAP); - DeviceProfileData deviceProfileData2 = new DeviceProfileData(); - deviceProfileData2.setConfiguration(new DefaultDeviceProfileConfiguration()); - deviceProfileData2.setTransportConfiguration(new CoapDeviceProfileTransportConfiguration()); - coapProfile.setProfileData(deviceProfileData); - coapProfile.setDefault(false); - coapProfile.setDefaultRuleChainId(null); - - coapDeviceProfileId = doPost("/api/deviceProfile", coapProfile, DeviceProfile.class).getId(); } @After @@ -743,144 +699,6 @@ public class DeviceControllerTest extends AbstractControllerTest { Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); } - @Test - public void testFetchPublishTelemetryCommandsForDefaultDevice() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setType("default"); - Device savedDevice = doPost("/api/device", device, Device.class); - Map commands = - doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); - - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - - assertThat(commands).hasSize(6); - assertThat(commands.get(HTTP)).isEqualTo(String.format("curl -v -X POST http://localhost:8080/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(commands.get(HTTPS)).isEqualTo(String.format("curl -v -X POST https://localhost:443/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(commands.get(MQTT)).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(commands.get(MQTTS)).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(commands.get(COAP)).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", - credentials.getCredentialsId())); - } - - @Test - public void testFetchPublishTelemetryCommandsForMqttDeviceWithAccessToken() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setDeviceProfileId(mqttDeviceProfileId); - - Device savedDevice = doPost("/api/device", device, Device.class); - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - - Map commands = - doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); - assertThat(commands).hasSize(2); - assertThat(commands.get(MQTT)).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -u %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(commands.get(MQTTS)).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - } - - @Test - public void testFetchPublishTelemetryCommandsForDeviceWithMqttBasicCreds() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setDeviceProfileId(mqttDeviceProfileId); - - Device savedDevice = doPost("/api/device", device, Device.class); - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - credentials.setCredentialsId(null); - credentials.setCredentialsType(DeviceCredentialsType.MQTT_BASIC); - BasicMqttCredentials basicMqttCredentials = new BasicMqttCredentials(); - String clientId = "testClientId"; - String userName = "testUsername"; - String password = "testPassword"; - basicMqttCredentials.setClientId(clientId); - basicMqttCredentials.setUserName(userName); - basicMqttCredentials.setPassword(password); - credentials.setCredentialsValue(JacksonUtil.toString(basicMqttCredentials)); - doPost("/api/device/credentials", credentials) - .andExpect(status().isOk()); - - Map commands = - doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); - assertThat(commands).hasSize(2); - assertThat(commands.get(MQTT)).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(commands.get(MQTTS)).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - } - - @Test - public void testFetchPublishTelemetryCommandsForDeviceWithX509Creds() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setDeviceProfileId(mqttDeviceProfileId); - - Device savedDevice = doPost("/api/device", device, Device.class); - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - credentials.setCredentialsId(null); - credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); - credentials.setCredentialsValue("testValue"); - doPost("/api/device/credentials", credentials) - .andExpect(status().isOk()); - - Map commands = - doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); - assertThat(commands).hasSize(1); - assertThat(commands.get(MQTTS)).isEqualTo(CHECK_DOCUMENTATION); - } - - @Test - public void testFetchPublishTelemetryCommandsForСoapDevice() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setDeviceProfileId(coapDeviceProfileId); - - Device savedDevice = doPost("/api/device", device, Device.class); - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - - Map commands = - doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); - assertThat(commands).hasSize(2); - assertThat(commands.get(COAP)).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", - credentials.getCredentialsId())); - } - - @Test - public void testFetchPublishTelemetryCommandsForСoapDeviceWithX509Creds() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setDeviceProfileId(coapDeviceProfileId); - - Device savedDevice = doPost("/api/device", device, Device.class); - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - credentials.setCredentialsId(null); - credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); - credentials.setCredentialsValue("testValue"); - doPost("/api/device/credentials", credentials) - .andExpect(status().isOk()); - - Map commands = - doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); - assertThat(commands).hasSize(1); - assertThat(commands.get(COAPS)).isEqualTo(CHECK_DOCUMENTATION); - } - @Test public void testSaveDeviceCredentials() throws Exception { Device device = new Device(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/TbDeviceConnectivitySslCertService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java similarity index 62% rename from dao/src/main/java/org/thingsboard/server/dao/device/TbDeviceConnectivitySslCertService.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java index 43b7f39d30..83f35d5566 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/TbDeviceConnectivitySslCertService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java @@ -15,7 +15,16 @@ */ package org.thingsboard.server.dao.device; +import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.server.common.data.Device; -public interface TbDeviceConnectivitySslCertService { - String getMqttSslCertificate(); +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Map; + +public interface DeviceConnectivityService { + + JsonNode findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException; + + String getSslServerChain(String protocol) throws IOException; } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index a029f27309..510250d264 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.device; -import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; @@ -46,8 +45,6 @@ public interface DeviceService extends EntityDaoService { DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId); - Map findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException; - Device findDeviceById(TenantId tenantId, DeviceId deviceId); ListenableFuture findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java index 5b169a6e79..fa5c61328b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java @@ -22,5 +22,5 @@ public class DeviceConnectivityInfo { private Boolean enabled; private String host; private String port; - private String sslCertPath; + private String sslServerPemPath; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java deleted file mode 100644 index e5851b43c4..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * 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.dao.device; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.FileUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.ResourceUtils; - -import javax.annotation.PostConstruct; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; - -@Service -@Slf4j -public class DeviceConnectivityMqttSslCertService implements TbDeviceConnectivitySslCertService { - - private String certificate; - @Autowired - private DeviceConnectivityConfiguration deviceConnectivityConfiguration; - - @PostConstruct - private void postConstruct() throws IOException { - String sslCertPath = deviceConnectivityConfiguration.getConnectivity() - .get(MQTTS) - .getSslCertPath(); - if (sslCertPath != null && ResourceUtils.resourceExists(this, sslCertPath)) { - certificate = FileUtils.readFileToString(new File(sslCertPath), StandardCharsets.UTF_8); - } - } - - @Override - public String getMqttSslCertificate() { - return certificate; - } -} 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 f34c1fa99d..5a6caefd58 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 @@ -38,7 +38,6 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration; @@ -48,7 +47,6 @@ import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; @@ -76,13 +74,9 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; -import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -91,18 +85,6 @@ import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateIds; import static org.thingsboard.server.dao.service.Validator.validatePageLink; import static org.thingsboard.server.dao.service.Validator.validateString; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.JSON_EXAMPLE_PAYLOAD; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.CHECK_DOCUMENTATION; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.SERVER_CHAIN_PEM; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCoapClientCommand; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCurlCommand; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getMosquittoPublishCommand; @Service("DeviceDaoService") @Slf4j @@ -134,12 +116,6 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException { - DeviceId deviceId = device.getId(); - log.trace("Executing findDevicePublishTelemetryCommands [{}]", deviceId); - validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); - - String defaultHostname = new URI(baseUrl).getHost(); - DeviceCredentials creds = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); - DeviceTransportType transportType = deviceProfile.getTransportType(); - - Map commands = new HashMap<>(); - switch (transportType) { - case DEFAULT: - Optional.ofNullable(getHttpPublishCommand(HTTP, defaultHostname, creds)).ifPresent(v -> commands.put(HTTP, v)); - Optional.ofNullable(getHttpPublishCommand(HTTPS, defaultHostname, creds)).ifPresent(v -> commands.put(HTTPS, v)); - Optional.ofNullable(getMqttPublishCommand(MQTT, defaultHostname, creds)).ifPresent(v -> commands.put(MQTT, v)); - Optional.ofNullable(getMqttPublishCommand(MQTTS, defaultHostname, creds)).ifPresent(v -> commands.put(MQTTS, v)); - Optional.ofNullable(getCoapPublishCommand(COAP, defaultHostname, creds)).ifPresent(v -> commands.put(COAP, v)); - Optional.ofNullable(getCoapPublishCommand(COAPS, defaultHostname, creds)).ifPresent(v -> commands.put(COAPS, v)); - break; - case MQTT: - MqttDeviceProfileTransportConfiguration transportConfiguration = - (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); - String topicName = transportConfiguration.getDeviceTelemetryTopic(); - TransportPayloadType payloadType = transportConfiguration.getTransportPayloadTypeConfiguration().getTransportPayloadType(); - String payload = (payloadType == TransportPayloadType.PROTOBUF) ? " -f protobufFileName" : " -m " + JSON_EXAMPLE_PAYLOAD; - - Optional.ofNullable(getMqttPublishCommand(MQTT, defaultHostname, topicName, creds, payload)).ifPresent(v -> commands.put(MQTT, v)); - Optional.ofNullable(getMqttPublishCommand(MQTTS, defaultHostname, topicName, creds, payload)).ifPresent(v -> commands.put(MQTTS, v)); - break; - case COAP: - Optional.ofNullable(getCoapPublishCommand(COAP, defaultHostname, creds)).ifPresent(v -> commands.put(COAP, v)); - Optional.ofNullable(getCoapPublishCommand(COAPS, defaultHostname, creds)).ifPresent(v -> commands.put(COAPS, v)); - break; - default: - commands.put(transportType.name(), CHECK_DOCUMENTATION); - } - - if (commands.containsKey(MQTTS) && deviceConnectivityMqttSslCertService.getMqttSslCertificate() != null) { - commands.put(SERVER_CHAIN_PEM, deviceConnectivityMqttSslCertService.getMqttSslCertificate()); - } - return commands; - } - @Override public Device findDeviceById(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceById [{}]", deviceId); @@ -747,44 +678,4 @@ public class DeviceServiceImpl extends AbstractCachedEntityService linuxMqttCommands.put(MQTT, v)); + Optional.ofNullable(getMqttPublishCommand(LINUX, MQTTS, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> linuxMqttCommands.put(MQTTS, v)); + + ObjectNode windowsMqttCommands = JacksonUtil.newObjectNode(); + Optional.ofNullable(getMqttPublishCommand(WINDOWS, MQTT, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> windowsMqttCommands.put(MQTT, v)); + Optional.ofNullable(getMqttPublishCommand(WINDOWS, MQTTS, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> windowsMqttCommands.put(MQTTS, v)); + + ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); + Optional.ofNullable(getMqttPublishCommand(DOCKER, MQTT, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> dockerMqttCommands.put(MQTT, v)); + Optional.ofNullable(getMqttPublishCommand(DOCKER, MQTTS, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> dockerMqttCommands.put(MQTTS, v)); + + mqttCommands.set(LINUX, linuxMqttCommands); + mqttCommands.set(WINDOWS, windowsMqttCommands); + mqttCommands.set(DOCKER, dockerMqttCommands); + + return mqttCommands; + } + + private String getMqttPublishCommand(String os, String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + if (MQTTS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + return CHECK_DOCUMENTATION; + } + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); + if (properties == null || !properties.getEnabled()) { + return null; + } + String mqttHost = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); + String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); + switch (os) { + case LINUX: + return getMosquittoPubPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + case WINDOWS: + return getMosquittoPubPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + case DOCKER: + return getDockerMosquittoClientsPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + default: + throw new IllegalArgumentException("Unsupported operating system: " + os); + } + } + + private JsonNode getCoapTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) { + ObjectNode coapCommands = JacksonUtil.newObjectNode(); + + ObjectNode linuxCoapCommands = JacksonUtil.newObjectNode(); + Optional.ofNullable(getCoapPublishCommand(LINUX, COAP, defaultHostname, deviceCredentials)) + .ifPresent(v -> linuxCoapCommands.put(COAP, v)); + Optional.ofNullable(getCoapPublishCommand(LINUX, COAPS, defaultHostname, deviceCredentials)) + .ifPresent(v -> linuxCoapCommands.put(COAPS, v)); + + coapCommands.set(LINUX, linuxCoapCommands); + return coapCommands; + } + + private String getCoapPublishCommand(String os, String protocol, String defaultHostname, DeviceCredentials deviceCredentials) { + if (COAPS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + return CHECK_DOCUMENTATION; + } + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); + if (properties == null || !properties.getEnabled()) { + return null; + } + String hostName = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); + String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); + + switch (os) { + case LINUX: + return getCoapClientCommand(protocol, hostName, port, deviceCredentials); + default: + throw new IllegalArgumentException("Unsupported operating system: " + os); + } + } +} 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 3257ea13d6..72eac8bdea 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 @@ -24,10 +24,13 @@ public class DeviceConnectivityUtil { public static final String HTTP = "http"; public static final String HTTPS = "https"; public static final String MQTT = "mqtt"; + public static final String LINUX = "linux"; + public static final String WINDOWS = "windows"; + public static final String DOCKER = "docker"; public static final String MQTTS = "mqtts"; public static final String COAP = "coap"; public static final String COAPS = "coaps"; - public static final String SERVER_CHAIN_PEM = "serverChainPem"; + public static final String MQTT_SSL_PEM_FILE_NAME = "tb-server-chain.pem"; public static final String CHECK_DOCUMENTATION = "Check documentation"; public static final String JSON_EXAMPLE_PAYLOAD = "\"{temperature:25}\""; @@ -36,10 +39,10 @@ public class DeviceConnectivityUtil { protocol, host, port, deviceCredentials.getCredentialsId()); } - public static String getMosquittoPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials, String payload) { + public static String getMosquittoPubPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { StringBuilder command = new StringBuilder("mosquitto_pub -d -q 1"); if (MQTTS.equals(protocol)) { - command.append(" --cafile tb-server-chain.pem"); + command.append(" --cafile pathToFile/" + MQTT_SSL_PEM_FILE_NAME); } command.append(" -h ").append(host).append(port == null ? "" : " -p " + port); command.append(" -t ").append(deviceTelemetryTopic); @@ -68,7 +71,47 @@ public class DeviceConnectivityUtil { default: return null; } - command.append(payload); + command.append(" -m " + JSON_EXAMPLE_PAYLOAD); + return command.toString(); + } + + public static String getDockerMosquittoClientsPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + StringBuilder command = new StringBuilder("docker run"); + if (MQTTS.equals(protocol)) { + command.append(" --volume pathToFile/" + MQTT_SSL_PEM_FILE_NAME + ":/tmp/" + MQTT_SSL_PEM_FILE_NAME); + } + command.append(" -it --rm thingsboard/mosquitto-clients pub"); + if (MQTTS.equals(protocol)) { + command.append(" --cafile tmp/" + MQTT_SSL_PEM_FILE_NAME); + } + command.append(" -h ").append(host).append(port == null ? "" : " -p " + port); + command.append(" -t ").append(deviceTelemetryTopic); + + switch (deviceCredentials.getCredentialsType()) { + case ACCESS_TOKEN: + command.append(" -u ").append(deviceCredentials.getCredentialsId()); + break; + case MQTT_BASIC: + BasicMqttCredentials credentials = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), + BasicMqttCredentials.class); + if (credentials != null) { + if (credentials.getClientId() != null) { + command.append(" -i ").append(credentials.getClientId()); + } + if (credentials.getUserName() != null) { + command.append(" -u ").append(credentials.getUserName()); + } + if (credentials.getPassword() != null) { + command.append(" -P ").append(credentials.getPassword()); + } + } else { + return null; + } + break; + default: + return null; + } + command.append(" -m " + JSON_EXAMPLE_PAYLOAD); return command.toString(); } From 753258c1ba52aeba89d2ce5357c53a9c9a2fbcf6 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 20 Jul 2023 13:03:24 +0300 Subject: [PATCH 097/168] AlarmsTableWidgetComponent, TimeseriesTableWidgetComponent - show pointer cursor if widget has rowClick action --- .../components/widget/lib/alarms-table-widget.component.html | 3 ++- .../components/widget/lib/alarms-table-widget.component.ts | 2 ++ .../widget/lib/timeseries-table-widget.component.html | 3 ++- .../components/widget/lib/timeseries-table-widget.component.ts | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html index 9a23e86bf8..1c03a37bbd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html @@ -160,7 +160,8 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index db2d04ea88..5612499c64 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -180,6 +180,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, public displayedColumns: string[] = []; public alarmsDatasource: AlarmsDatasource; public noDataDisplayMessageText: string; + public hasRowAction: boolean; private setCellButtonAction: boolean; private cellContentCache: Array = []; @@ -493,6 +494,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, } this.setCellButtonAction = !!(actionCellDescriptors.length + this.ctx.actionsApi.getActionDescriptors('actionCellButton').length); + this.hasRowAction = !!this.ctx.actionsApi.getActionDescriptors('rowClick').length; if (this.setCellButtonAction) { this.displayedColumns.push('actions'); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html index d0ca2f23d1..65c4fbe0ee 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html @@ -99,7 +99,8 @@ - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts index 78f6203b1b..e6c3bc7e24 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts @@ -161,6 +161,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI public sources: TimeseriesTableSource[]; public sourceIndex: number; public noDataDisplayMessageText: string; + public hasRowAction: boolean; private setCellButtonAction: boolean; private cellContentCache: Array = []; @@ -300,6 +301,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI this.ctx.widgetActions = [this.searchAction, this.columnDisplayAction]; this.setCellButtonAction = !!this.ctx.actionsApi.getActionDescriptors('actionCellButton').length; + this.hasRowAction = !!this.ctx.actionsApi.getActionDescriptors('rowClick').length; this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true; this.columnDisplayAction.show = isDefined(this.settings.enableSelectColumnDisplay) ? this.settings.enableSelectColumnDisplay : true; From 9c9cac9bd1969499cac16c3f08227e6d7ce2b5c6 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 20 Jul 2023 13:54:31 +0300 Subject: [PATCH 098/168] fix entity view delete test --- .../server/controller/EntityViewControllerTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java index a65769f3f1..cb41019f6f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java @@ -43,6 +43,7 @@ import org.springframework.test.web.servlet.ResultActions; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.StringUtils; @@ -253,7 +254,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { doGet("/api/entityView/" + entityIdStr) .andExpect(status().isNotFound()) - .andExpect(statusReason(containsString(msgErrorNoFound("Entity view",entityIdStr)))); + .andExpect(statusReason(containsString(msgErrorNoFound(EntityType.ENTITY_VIEW.getNormalName(), entityIdStr)))); } @Test @@ -425,12 +426,12 @@ public class EntityViewControllerTest extends AbstractControllerTest { testNotifyEntityBroadcastEntityStateChangeEventMany(new EntityView(), new EntityView(), tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, - ActionType.ADDED, ActionType.ADDED, cntEntity, 0, cntEntity*2, 0); + ActionType.ADDED, ActionType.ADDED, cntEntity, 0, cntEntity * 2, 0); testNotifyEntityBroadcastEntityStateChangeEventMany(new EntityView(), new EntityView(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ASSIGNED_TO_CUSTOMER, ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, - cntEntity*2, 3); + cntEntity * 2, 3); } @Test From c913b08b53b863e5c7d249ebfc5cf61ece5880e8 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 20 Jul 2023 15:13:11 +0300 Subject: [PATCH 099/168] UI: Fixed entity select component --- .../components/entity/entity-select.component.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index ccc4a0a079..9427c2ff73 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -125,16 +125,23 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte if (value.id === NULL_UUID) { value.id = null; } + if (value.entityType === AliasEntityType.CURRENT_TENANT + || value.entityType === AliasEntityType.CURRENT_USER + || value.entityType === AliasEntityType.CURRENT_USER_OWNER) { + value.id = NULL_UUID; + } else if (value.entityType === AliasEntityType.CURRENT_CUSTOMER && !value.id) { + this.modelValue.id = NULL_UUID; + } this.modelValue = value; - this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: true}); - this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: true}); + this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: false}); + this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: false}); } else { this.modelValue = { entityType: this.defaultEntityType, id: null }; - this.entitySelectFormGroup.get('entityType').patchValue(this.defaultEntityType, {emitEvent: true}); - this.entitySelectFormGroup.get('entityId').patchValue(null, {emitEvent: true}); + this.entitySelectFormGroup.get('entityType').patchValue(this.defaultEntityType, {emitEvent: false}); + this.entitySelectFormGroup.get('entityId').patchValue(null, {emitEvent: false}); } } From e3ef58c6038dd2e9e949ffaeb18a7d8991611f69 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 20 Jul 2023 15:33:02 +0300 Subject: [PATCH 100/168] added notnull check for http commands --- .../server/dao/device/DeviceСonnectivityServiceImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java index 7ae49276b8..e062441559 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java @@ -119,8 +119,10 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService private JsonNode getHttpTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) { ObjectNode httpCommands = JacksonUtil.newObjectNode(); - httpCommands.put(HTTP, getHttpPublishCommand(HTTP, defaultHostname, deviceCredentials)); - httpCommands.put(HTTPS, getHttpPublishCommand(HTTPS, defaultHostname, deviceCredentials)); + Optional.ofNullable(getHttpPublishCommand(HTTP, defaultHostname, deviceCredentials)) + .ifPresent(v -> httpCommands.put(HTTP, v)); + Optional.ofNullable(getHttpPublishCommand(HTTPS, defaultHostname, deviceCredentials)) + .ifPresent(v -> httpCommands.put(HTTPS, v)); return httpCommands; } From 1c601a6e7ded514f791550c389441ef1ff66cb27 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 20 Jul 2023 16:39:44 +0300 Subject: [PATCH 101/168] UI: Change field label assign customer --- .../home/components/wizard/device-wizard-dialog.component.html | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 8d55b4bc6a..08d71e1229 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -76,7 +76,7 @@
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 c5ec1fca40..5254d7b654 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -937,7 +937,8 @@ "search": "Search customers", "selected-customers": "{ count, plural, =1 {1 customer} other {# customers} } selected", "edges": "Customer edge instances", - "manage-edges": "Manage edges" + "manage-edges": "Manage edges", + "assign-customer": "Assign customer" }, "datetime": { "date-from": "Date from", From fc499c74e3599d49f1349479c02947f80efbeddc Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 20 Jul 2023 17:22:33 +0300 Subject: [PATCH 102/168] deleted redundant imports --- .../server/controller/DeviceController.java | 2 -- .../server/controller/DeviceControllerTest.java | 10 ---------- .../thingsboard/server/dao/device/DeviceService.java | 2 -- .../server/dao/device/DeviceServiceImpl.java | 1 - 4 files changed, 15 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 07adb1ef1c..3eb6202aea 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -73,7 +73,6 @@ import org.thingsboard.server.service.entitiy.device.TbDeviceService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.annotation.Nullable; import javax.validation.Valid; @@ -135,7 +134,6 @@ public class DeviceController extends BaseController { private final TbDeviceService tbDeviceService; - private final SystemSecurityService systemSecurityService; @ApiOperation(value = "Get Device (getDeviceById)", notes = "Fetch the Device object based on the provided Device Id. " + diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 287e383317..9ab5f7fde8 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -34,15 +34,12 @@ import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileType; -import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackageInfo; @@ -52,16 +49,10 @@ 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.device.credentials.BasicMqttCredentials; -import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceCredentialsId; 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.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -93,7 +84,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; - @ContextConfiguration(classes = {DeviceControllerTest.Config.class}) @DaoSqlTest public class DeviceControllerTest extends AbstractControllerTest { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index 510250d264..a90ea9a572 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -36,9 +36,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.entity.EntityDaoService; -import java.net.URISyntaxException; import java.util.List; -import java.util.Map; import java.util.UUID; public interface DeviceService extends EntityDaoService { 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 5a6caefd58..3f9ee12dda 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 @@ -96,7 +96,6 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Thu, 20 Jul 2023 17:36:55 +0300 Subject: [PATCH 103/168] Fix for update JSON attribute widget required time-series data key instead attribute key --- .../src/main/data/json/system/widget_bundles/input_widgets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index ca2e16e4dd..e8856b5d97 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -498,7 +498,7 @@ "settingsSchema": "", "dataKeySettingsSchema": "{}", "settingsDirective": "tb-update-json-attribute-widget-settings", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"attributeScope\":\"SERVER_SCOPE\",\"showLabel\":true,\"attributeRequired\":true,\"showResultMessage\":true},\"title\":\"Update JSON attribute\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"widgetMode\":\"ATTRIBUTE\",\"attributeScope\":\"SERVER_SCOPE\",\"showLabel\":true,\"attributeRequired\":true,\"showResultMessage\":true},\"title\":\"Update JSON attribute\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}" } } ] From 36574f32306f6038a6d3a30c8c436b5b960dcd1a Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 21 Jul 2023 10:32:08 +0300 Subject: [PATCH 104/168] UI: Remove translate --- ui-ngx/src/assets/locale/locale.constant-ca_ES.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-cs_CZ.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-da_DK.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-es_ES.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-fr_FR.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-ko_KR.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-sl_SI.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-tr_TR.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-zh_CN.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-zh_TW.json | 3 +-- 11 files changed, 11 insertions(+), 22 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json index 349d13da2c..77edc26187 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json @@ -1376,8 +1376,7 @@ "device-configuration": "Configuració del dispositiu", "transport-configuration": "Configuració del transport", "wizard": { - "device-details": "Detalls del dispositiu", - "customer-to-assign-device": "Client al que assignar el dispositiu" + "device-details": "Detalls del dispositiu" }, "unassign-devices-from-edge-title": "Està segur de que desitja desassignar {count, plural, =1 {1 dispositivo} other {# dispositivos} }?", "unassign-devices-from-edge-text": "Després de la confirmació, tots els dispositius seleccionats quedaran sense assignar i la vora no podrà accedir a ells." diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index 52873b4d70..d89971cd0c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -1018,8 +1018,7 @@ "device-configuration": "Konfigurace zařízení", "transport-configuration": "Konfigurace přenosu", "wizard": { - "device-details": "Detail zařízení", - "customer-to-assign-device": "Přiřadit zařízení zákazníkovi" + "device-details": "Detail zařízení" }, "unassign-devices-from-edge-title": "Jste se jisti, že chcete odebrat { count, plural, =1 {1 zařízení} other {# zařízení} }?", "unassign-devices-from-edge-text": "Po potvrzení budou všechna vybraná zařízení odebrána a nebudou pro edge dostupná." diff --git a/ui-ngx/src/assets/locale/locale.constant-da_DK.json b/ui-ngx/src/assets/locale/locale.constant-da_DK.json index 2c1df70902..4e3b3ca779 100644 --- a/ui-ngx/src/assets/locale/locale.constant-da_DK.json +++ b/ui-ngx/src/assets/locale/locale.constant-da_DK.json @@ -1095,8 +1095,7 @@ "device-configuration": "Enhedskonfiguration", "transport-configuration": "Transportkonfiguration", "wizard": { - "device-details": "Enhedsoplysninger", - "customer-to-assign-device": "Kunden skal tildele enheden" + "device-details": "Enhedsoplysninger" } }, "device-profile": { 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 5254d7b654..e0c07480cf 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1375,8 +1375,7 @@ "device-configuration": "Device configuration", "transport-configuration": "Transport configuration", "wizard": { - "device-details": "Device details", - "customer-to-assign-device": "Customer to assign the device" + "device-details": "Device details" }, "unassign-devices-from-edge-title": "Are you sure you want to unassign { count, plural, =1 {1 device} other {# devices} }?", "unassign-devices-from-edge-text": "After the confirmation all selected devices will be unassigned and won't be accessible by the edge." diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index 6518e03f58..d579de1a1f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -1325,8 +1325,7 @@ "device-configuration": "Configuración del dispositivo", "transport-configuration": "Configuración del transporte", "wizard": { - "device-details": "Detalles del dispositivo", - "customer-to-assign-device": "Cliente al que asignar el dispositivo" + "device-details": "Detalles del dispositivo" }, "unassign-devices-from-edge-title": "¿Está seguro de que desea desasignar {count, plural, =1 {1 dispositivo} other {# dispositivos} }?", "unassign-devices-from-edge-text": "Después de la confirmación, todos los dispositivos seleccionados quedarán sin asignar y el Edge no podrá acceder a ellos." diff --git a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json index 19f92e5a7c..56712eeeb5 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -1053,8 +1053,7 @@ "device-configuration": "Configuration du dipositif", "transport-configuration": "Configuration du transport", "wizard": { - "device-details": "Détails du dispositif", - "customer-to-assign-device": "Client auquel assigner le dispositif" + "device-details": "Détails du dispositif" } }, "device-profile": { diff --git a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json index 758482f578..0fd1ba039d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json +++ b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json @@ -913,8 +913,7 @@ "device-configuration": "장치 설정", "transport-configuration": "전송 설정", "wizard": { - "device-details": "장치 상세 정보", - "customer-to-assign-device": "장치에 할당할 커스터머" + "device-details": "장치 상세 정보" } }, "device-profile": { diff --git a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json index 8aced0ddc6..fcc2f6f867 100644 --- a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json +++ b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json @@ -913,8 +913,7 @@ "device-configuration": "Device configuration", "transport-configuration": "Transport configuration", "wizard": { - "device-details": "Device details", - "customer-to-assign-device": "Customer to assign the device" + "device-details": "Device details" } }, "device-profile": { diff --git a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json index b175a2d51a..cd7e31e97f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -1021,8 +1021,7 @@ "device-configuration": "Cihaz yapılandırması", "transport-configuration": "Aktarım yapılandırması", "wizard": { - "device-details": "Cihaz ayrıntıları", - "customer-to-assign-device": "Cihazı atamak için kullanıcı grubu" + "device-details": "Cihaz ayrıntıları" }, "unassign-devices-from-edge-title": "{ count, plural, =1 {1 cihazın} other {# cihazın} } atamasını kaldırmak istediğinizden emin misiniz?", "unassign-devices-from-edge-text": "Onaydan sonra, seçilen tüm cihazların ataması kaldırılacak ve uç tarafından erişilemeyecek." 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 b39e10e46c..2a9645f982 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -1221,8 +1221,7 @@ "device-configuration": "设备配置", "transport-configuration": "传输配置", "wizard": { - "device-details": "设备详细信息", - "customer-to-assign-device": "客户分配设备" + "device-details": "设备详细信息" }, "unassign-devices-from-edge-title": "确定要取消分配 { count, plural, =1 {1 个设备} other {# 个设备} } 吗?", "unassign-devices-from-edge-text": "确认后,设备将被取消分配,边缘将无法访问。" diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json index f2cce81824..3bc75f062c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json @@ -1134,8 +1134,7 @@ "device-configuration": "設備配置", "transport-configuration": "傳輸配置", "wizard": { - "device-details": "設備詳情", - "customer-to-assign-device": "客戶指定設備" + "device-details": "設備詳情" }, "unassign-devices-from-edge-title": "您確定要解除邊緣設備 { count, plural, =1 {1 device} other {# devices} }的指定嗎?", "unassign-devices-from-edge-text": "確認後邊緣指定設備將解除指定及其所有相關資料將無法恢復。" From d99c08fbbbde10a151b19668fc27e9bcfbb39d72 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 21 Jul 2023 12:52:15 +0300 Subject: [PATCH 105/168] changed response data structure --- .../DeviceConnectivityControllerTest.java | 38 +++------- .../DeviceСonnectivityServiceImpl.java | 75 +++++++++---------- 2 files changed, 45 insertions(+), 68 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index 8e27857878..3b10695e62 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -213,7 +213,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { credentials.getCredentialsId())); - JsonNode linuxMqttCommands = commands.get(MQTT).get(LINUX); + JsonNode linuxMqttCommands = commands.get(MQTT); assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + "-u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); @@ -221,11 +221,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - JsonNode windowsMqttCommands = commands.get(MQTT).get(WINDOWS); - assertThat(windowsMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + - "-u %s -m \"{temperature:25}\"", - credentials.getCredentialsId())); - JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + @@ -235,13 +230,11 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - JsonNode linuxCoapCommands = commands.get(COAP).get(LINUX); + JsonNode linuxCoapCommands = commands.get(COAP); assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry " + - "-t json -e \"{temperature:25}\"", - credentials.getCredentialsId())); + "-t json -e \"{temperature:25}\"", credentials.getCredentialsId())); assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry" + - " -t json -e \"{temperature:25}\"", - credentials.getCredentialsId())); + " -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } @Test @@ -258,7 +251,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); assertThat(commands).hasSize(1); - JsonNode linuxMqttCommands = commands.get(MQTT).get(LINUX); + JsonNode linuxMqttCommands = commands.get(MQTT); assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + "-u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); @@ -266,11 +259,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "-t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - JsonNode windowsMqttCommands = commands.get(MQTT).get(WINDOWS); - assertThat(windowsMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + - "-u %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + @@ -303,12 +291,11 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doPost("/api/device/credentials", credentials) .andExpect(status().isOk()); - JsonNode commands = doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); assertThat(commands).hasSize(1); - JsonNode linuxMqttCommands = commands.get(MQTT).get(LINUX); + JsonNode linuxMqttCommands = commands.get(MQTT); assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + "-i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); @@ -316,12 +303,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "-t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - JsonNode windowsMqttCommands = commands.get(MQTT).get(WINDOWS); - assertThat(windowsMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + - "-i %s -u %s -P %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - - JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + " -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", @@ -349,8 +330,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { JsonNode commands = doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); assertThat(commands).hasSize(1); - assertThat(commands.get(MQTT).get(LINUX).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); - assertThat(commands.get(MQTT).get(WINDOWS).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(MQTT).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); assertThat(commands.get(MQTT).get(DOCKER).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); } @@ -368,7 +348,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); assertThat(commands).hasSize(1); - JsonNode linuxCommands = commands.get(COAP).get(LINUX); + JsonNode linuxCommands = commands.get(COAP); assertThat(linuxCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); assertThat(linuxCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", @@ -393,6 +373,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { JsonNode commands = doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); assertThat(commands).hasSize(1); - assertThat(commands.get(COAP).get(LINUX).get(COAPS).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(COAP).get(COAPS).asText()).isEqualTo(CHECK_DOCUMENTATION); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java index e062441559..694f1cb8ee 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java @@ -85,22 +85,27 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService ObjectNode commands = JacksonUtil.newObjectNode(); switch (transportType) { case DEFAULT: - commands.set(HTTP, getHttpTransportPublishCommands(defaultHostname, creds)); - commands.set(MQTT, getMqttTransportPublishCommands(defaultHostname, creds)); - commands.set(COAP, getCoapTransportPublishCommands(defaultHostname, creds)); + Optional.ofNullable(getHttpTransportPublishCommands(defaultHostname, creds)) + .ifPresent(v -> commands.set(HTTP, v)); + Optional.ofNullable(getMqttTransportPublishCommands(defaultHostname, creds)) + .ifPresent(v -> commands.set(MQTT, v)); + Optional.ofNullable(getCoapTransportPublishCommands(defaultHostname, creds)) + .ifPresent(v -> commands.set(COAP, v)); break; case MQTT: MqttDeviceProfileTransportConfiguration transportConfiguration = (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); String topicName = transportConfiguration.getDeviceTelemetryTopic(); - commands.set(MQTT, getMqttTransportPublishCommands(defaultHostname, topicName, creds)); + Optional.ofNullable(getMqttTransportPublishCommands(defaultHostname, topicName, creds)) + .ifPresent(v -> commands.set(MQTT, v)); break; case COAP: - commands.set(COAP, getCoapTransportPublishCommands(defaultHostname, creds)); + Optional.ofNullable(getCoapTransportPublishCommands(defaultHostname, creds)) + .ifPresent(v -> commands.set(COAP, v)); break; default: - commands.set(transportType.name(), JacksonUtil.toJsonNode(CHECK_DOCUMENTATION)); + commands.put(transportType.name(), CHECK_DOCUMENTATION); } return commands; } @@ -123,7 +128,7 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService .ifPresent(v -> httpCommands.put(HTTP, v)); Optional.ofNullable(getHttpPublishCommand(HTTPS, defaultHostname, deviceCredentials)) .ifPresent(v -> httpCommands.put(HTTPS, v)); - return httpCommands; + return httpCommands.isEmpty() ? null : httpCommands; } private String getHttpPublishCommand(String protocol, String defaultHostname, DeviceCredentials deviceCredentials) { @@ -145,32 +150,22 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService private JsonNode getMqttTransportPublishCommands(String defaultHostname, String topic, DeviceCredentials deviceCredentials) { ObjectNode mqttCommands = JacksonUtil.newObjectNode(); - ObjectNode linuxMqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getMqttPublishCommand(LINUX, MQTT, defaultHostname, topic, deviceCredentials)) - .ifPresent(v -> linuxMqttCommands.put(MQTT, v)); - Optional.ofNullable(getMqttPublishCommand(LINUX, MQTTS, defaultHostname, topic, deviceCredentials)) - .ifPresent(v -> linuxMqttCommands.put(MQTTS, v)); - - ObjectNode windowsMqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getMqttPublishCommand(WINDOWS, MQTT, defaultHostname, topic, deviceCredentials)) - .ifPresent(v -> windowsMqttCommands.put(MQTT, v)); - Optional.ofNullable(getMqttPublishCommand(WINDOWS, MQTTS, defaultHostname, topic, deviceCredentials)) - .ifPresent(v -> windowsMqttCommands.put(MQTTS, v)); + Optional.ofNullable(getMqttPublishCommand(MQTT, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> mqttCommands.put(MQTT, v)); + Optional.ofNullable(getMqttPublishCommand(MQTTS, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> mqttCommands.put(MQTTS, v)); ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getMqttPublishCommand(DOCKER, MQTT, defaultHostname, topic, deviceCredentials)) + Optional.ofNullable(getDockerMqttPublishCommand(MQTT, defaultHostname, topic, deviceCredentials)) .ifPresent(v -> dockerMqttCommands.put(MQTT, v)); - Optional.ofNullable(getMqttPublishCommand(DOCKER, MQTTS, defaultHostname, topic, deviceCredentials)) + Optional.ofNullable(getDockerMqttPublishCommand(MQTTS, defaultHostname, topic, deviceCredentials)) .ifPresent(v -> dockerMqttCommands.put(MQTTS, v)); - mqttCommands.set(LINUX, linuxMqttCommands); - mqttCommands.set(WINDOWS, windowsMqttCommands); mqttCommands.set(DOCKER, dockerMqttCommands); - - return mqttCommands; + return mqttCommands.isEmpty() ? null : mqttCommands; } - private String getMqttPublishCommand(String os, String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + private String getMqttPublishCommand(String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { if (MQTTS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { return CHECK_DOCUMENTATION; } @@ -180,29 +175,31 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService } String mqttHost = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); - switch (os) { - case LINUX: - return getMosquittoPubPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); - case WINDOWS: - return getMosquittoPubPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); - case DOCKER: - return getDockerMosquittoClientsPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); - default: - throw new IllegalArgumentException("Unsupported operating system: " + os); + return getMosquittoPubPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + } + + private String getDockerMqttPublishCommand(String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + if (MQTTS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + return CHECK_DOCUMENTATION; + } + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); + if (properties == null || !properties.getEnabled()) { + return null; } + String mqttHost = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); + String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); + return getDockerMosquittoClientsPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); } private JsonNode getCoapTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) { ObjectNode coapCommands = JacksonUtil.newObjectNode(); - ObjectNode linuxCoapCommands = JacksonUtil.newObjectNode(); Optional.ofNullable(getCoapPublishCommand(LINUX, COAP, defaultHostname, deviceCredentials)) - .ifPresent(v -> linuxCoapCommands.put(COAP, v)); + .ifPresent(v -> coapCommands.put(COAP, v)); Optional.ofNullable(getCoapPublishCommand(LINUX, COAPS, defaultHostname, deviceCredentials)) - .ifPresent(v -> linuxCoapCommands.put(COAPS, v)); + .ifPresent(v -> coapCommands.put(COAPS, v)); - coapCommands.set(LINUX, linuxCoapCommands); - return coapCommands; + return coapCommands.isEmpty() ? null : coapCommands; } private String getCoapPublishCommand(String os, String protocol, String defaultHostname, DeviceCredentials deviceCredentials) { From 2ad30336ea214307e9c5dc86d43b64bf17987877 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 21 Jul 2023 13:51:49 +0300 Subject: [PATCH 106/168] deleted valur for mqqtt docker command when creds are X509 --- .../controller/DeviceConnectivityControllerTest.java | 8 ++++---- .../server/dao/device/DeviceСonnectivityServiceImpl.java | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index 3b10695e62..b138778025 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -213,11 +213,11 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { credentials.getCredentialsId())); - JsonNode linuxMqttCommands = commands.get(MQTT); - assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + + JsonNode mqttCommands = commands.get(MQTT); + assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + "-u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(linuxMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + + assertThat(mqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); @@ -331,7 +331,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); assertThat(commands).hasSize(1); assertThat(commands.get(MQTT).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); - assertThat(commands.get(MQTT).get(DOCKER).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(MQTT).get(DOCKER)).isNull(); } @Test diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java index 694f1cb8ee..284115ffb2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java @@ -161,7 +161,9 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService Optional.ofNullable(getDockerMqttPublishCommand(MQTTS, defaultHostname, topic, deviceCredentials)) .ifPresent(v -> dockerMqttCommands.put(MQTTS, v)); - mqttCommands.set(DOCKER, dockerMqttCommands); + if (!dockerMqttCommands.isEmpty()) { + mqttCommands.set(DOCKER, dockerMqttCommands); + } return mqttCommands.isEmpty() ? null : mqttCommands; } @@ -179,9 +181,6 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService } private String getDockerMqttPublishCommand(String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { - if (MQTTS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { - return CHECK_DOCUMENTATION; - } DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); if (properties == null || !properties.getEnabled()) { return null; From 9d20fa7d9e2c4857a1102a90ef3e5c5521df60e1 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 21 Jul 2023 16:12:34 +0300 Subject: [PATCH 107/168] added additional validation to ActionTypeTest and TbMsgTypeTest --- .../thingsboard/server/common/data/audit/ActionTypeTest.java | 2 ++ .../org/thingsboard/server/common/data/msg/TbMsgTypeTest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java index b76b0fc2b7..8c602c3cff 100644 --- a/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java +++ b/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java @@ -56,6 +56,8 @@ class ActionTypeTest { for (var type : types) { if (typesWithNullRuleEngineMsgType.contains(type)) { assertThat(type.getRuleEngineMsgType()).isEmpty(); + } else { + assertThat(type.getRuleEngineMsgType()).isPresent(); } } } 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 1323b7359d..a37eb31d72 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 @@ -62,6 +62,8 @@ class TbMsgTypeTest { for (var type : tbMsgTypes) { if (typesWithNullRuleNodeConnection.contains(type)) { assertThat(type.getRuleNodeConnection()).isNull(); + } else { + assertThat(type.getRuleNodeConnection()).isNotNull(); } } } From d52b67cc1b6c569fea2aa6f046971345fbb6a10b Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 21 Jul 2023 16:29:04 +0300 Subject: [PATCH 108/168] UI: Refactoring --- .../components/entity/entity-select.component.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index 9427c2ff73..93452e4620 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -122,16 +122,6 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte writeValue(value: EntityId | null): void { if (value != null) { - if (value.id === NULL_UUID) { - value.id = null; - } - if (value.entityType === AliasEntityType.CURRENT_TENANT - || value.entityType === AliasEntityType.CURRENT_USER - || value.entityType === AliasEntityType.CURRENT_USER_OWNER) { - value.id = NULL_UUID; - } else if (value.entityType === AliasEntityType.CURRENT_CUSTOMER && !value.id) { - this.modelValue.id = NULL_UUID; - } this.modelValue = value; this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: false}); this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: false}); @@ -156,8 +146,6 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte || this.modelValue.entityType === AliasEntityType.CURRENT_USER || this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER) { this.modelValue.id = NULL_UUID; - } else if (this.modelValue.entityType === AliasEntityType.CURRENT_CUSTOMER && !this.modelValue.id) { - this.modelValue.id = NULL_UUID; } if (this.modelValue.entityType && this.modelValue.id) { From 26044fc0358e11de99ad9cededc5ef5e1f87adec Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 21 Jul 2023 17:53:21 +0300 Subject: [PATCH 109/168] UI: Updated show device connectivity commands and detect operating system from user --- ui-ngx/src/app/app.component.ts | 5 + ui-ngx/src/app/core/http/device.service.ts | 10 +- ui-ngx/src/app/core/utils.ts | 26 +- ...e-check-connectivity-dialog.component.html | 363 +++++++++++++----- ...e-check-connectivity-dialog.component.scss | 19 +- ...ice-check-connectivity-dialog.component.ts | 89 ++++- ui-ngx/src/app/shared/models/device.models.ts | 25 ++ ui-ngx/src/assets/docker.svg | 1 + .../help/en_US/device/install_coap_client.md | 40 -- .../assets/help/en_US/device/install_curl.md | 34 -- .../help/en_US/device/install_mqtt_client.md | 38 -- ui-ngx/src/assets/linux.svg | 1 + .../assets/locale/locale.constant-en_US.json | 15 +- ui-ngx/src/assets/macos.svg | 1 + ui-ngx/src/assets/windows.svg | 1 + ui-ngx/src/form.scss | 7 + 16 files changed, 435 insertions(+), 240 deletions(-) create mode 100644 ui-ngx/src/assets/docker.svg delete mode 100644 ui-ngx/src/assets/help/en_US/device/install_coap_client.md delete mode 100644 ui-ngx/src/assets/help/en_US/device/install_curl.md delete mode 100644 ui-ngx/src/assets/help/en_US/device/install_mqtt_client.md create mode 100644 ui-ngx/src/assets/linux.svg create mode 100644 ui-ngx/src/assets/macos.svg create mode 100644 ui-ngx/src/assets/windows.svg diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index 8f612da5a1..627fc53608 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -94,6 +94,11 @@ export class AppComponent implements OnInit { ) ); + this.matIconRegistry.addSvgIcon('windows', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/windows.svg')); + this.matIconRegistry.addSvgIcon('macos', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/macos.svg')); + this.matIconRegistry.addSvgIcon('linux', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/linux.svg')); + this.matIconRegistry.addSvgIcon('docker', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/docker.svg')); + this.storageService.testLocalStorage(); this.setupTranslate(); diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index 44e91e43f8..8dff1e7ebc 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -25,8 +25,10 @@ import { ClaimResult, Device, DeviceCredentials, - DeviceInfo, DeviceInfoQuery, - DeviceSearchQuery + DeviceInfo, + DeviceInfoQuery, + DeviceSearchQuery, + PublishTelemetryCommand } from '@app/shared/models/device.models'; import { EntitySubtype } from '@app/shared/models/entity-type.models'; import { AuthService } from '@core/auth/auth.service'; @@ -208,8 +210,8 @@ export class DeviceService { return this.http.post('/api/device/bulk_import', entitiesData, defaultHttpOptionsFromConfig(config)); } - public getDevicePublishTelemetryCommands(deviceId: string, config?: RequestConfig): Observable<{[key: string]: string}> { - return this.http.get<{[key: string]: string}>(`/api/device/${deviceId}/commands`, defaultHttpOptionsFromConfig(config)); + public getDevicePublishTelemetryCommands(deviceId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/device-connectivity/${deviceId}`, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index d6a3c3c6e3..c823c2bfea 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -355,9 +355,7 @@ const SNAKE_CASE_REGEXP = /[A-Z]/g; export function snakeCase(name: string, separator: string): string { separator = separator || '_'; - return name.replace(SNAKE_CASE_REGEXP, (letter, pos) => { - return (pos ? separator : '') + letter.toLowerCase(); - }); + return name.replace(SNAKE_CASE_REGEXP, (letter, pos) => (pos ? separator : '') + letter.toLowerCase()); } export function getDescendantProp(obj: any, path: string): any { @@ -776,3 +774,25 @@ export function genNextLabel(name: string, datasources: Datasource[]): string { } return label; } + +export const getOS = (): string => { + const userAgent = window.navigator.userAgent.toLowerCase(); + const macosPlatforms = /(macintosh|macintel|macppc|mac68k|macos|mac_powerpc)/i; + const windowsPlatforms = /(win32|win64|windows|wince)/i; + const iosPlatforms = /(iphone|ipad|ipod|darwin|ios)/i; + let os = null; + + if (macosPlatforms.test(userAgent)) { + os = 'macos'; + } else if (iosPlatforms.test(userAgent)) { + os = 'ios'; + } else if (windowsPlatforms.test(userAgent)) { + os = 'windows'; + } else if (/android/.test(userAgent)) { + os = 'android'; + } else if (/linux/.test(userAgent)) { + os = 'linux'; + } + + return os; +}; diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html index a0991570eb..a595487521 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html @@ -59,121 +59,235 @@ {{ deviceTransportTypeTranslationMap.get(DeviceTransportType.LWM2M) | translate }} -
+
-
device.connectivity.use-following-instructions
-
- device.connectivity.install-curl - -
-
-
device.connectivity.http-command
- -
-
-
device.connectivity.https-command
- -
+
device.connectivity.use-following-instructions
+ + + + + Windows + + +
+
+
device.connectivity.install-necessary-client-tools
+
device.connectivity.install-curl-windows
+
+ + +
+
+
+ + + + MacOS + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ + +
+
+
+ + + + Linux + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ + +
+
+
+
-
-
device.connectivity.use-following-instructions
-
- device.connectivity.install-mqtt-client - -
-
-
-
device.connectivity.mqtt-command
- -
-
-
-
device.connectivity.mqtts-command
- -
- -
device.connectivity.mqtts-x509-command
- -
-
+
device.connectivity.use-following-instructions
+ + + + + Windows + + +
+
+
device.connectivity.install-necessary-client-tools
+
Coming Soon!!!!
+
+ + +
+
+
+ + + + MacOS + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ + +
+
+
+ + + + Linux + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ + +
+
+
+ + + + Docker + + +
+ + +
+
+
+
-
-
device.connectivity.use-following-instructions
-
- device.connectivity.install-coap-cli - -
-
-
-
device.connectivity.coap-command
- -
-
-
-
device.connectivity.coaps-command
- -
- -
device.connectivity.coaps-x509-command
- -
-
+
device.connectivity.use-following-instructions
+ + + + + MacOS + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ + +
+
+
+ + + + Linux + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ + +
+
+
+ + + + Docker + + +
+ + +
+
+
+
-
device.connectivity.snmp-command
-
- - - {{ 'action.see-documentation' | translate }} - open_in_new - - +
+ +
-
device.connectivity.lwm2m-command
-
- - - {{ 'action.see-documentation' | translate }} - open_in_new - - +
+ +
@@ -224,3 +338,44 @@
attribute.no-latest-telemetry
+ + +
+
+
device.connectivity.execute-following-command
+ + {{ cmd.noSecLabel }} + {{ cmd.secLabel }} + +
+ + + + + +
+ +
+ + + + +
+
+
+
+ + + + diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss index e7c88bb2cb..1a95da0a14 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss @@ -68,6 +68,10 @@ font-size: 14px; } + .tb-flex-1 { + flex: 1; + } + .tb-form-table-body { max-height: 88px; overflow-y: auto; @@ -84,6 +88,10 @@ } } + .tb-install-windows { + min-height: 42px; + } + @media #{$mat-sm} { width: 470px; } @@ -112,12 +120,13 @@ .code-wrapper { padding: 0; pre[class*=language-] { + margin: 0; background: #F3F6FA; border-color: #305680; } } button.clipboard-btn { - right: 0; + right: -2px; p { color: #305680; } @@ -148,4 +157,12 @@ box-sizing: initial; } } + + .tabs-icon { + margin-right: 8px; + } + + .tb-form-panel.tb-tab-body { + padding: 16px 0 0; + } } diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts index 8a427512aa..f185d88c6a 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts @@ -38,16 +38,19 @@ import { BasicTransportType, DeviceTransportType, deviceTransportTypeTranslationMap, - NetworkTransportType + NetworkTransportType, + PublishTelemetryCommand } from '@shared/models/device.models'; import { UserSettingsService } from '@core/http/user-settings.service'; import { ActionPreferencesUpdateUserSettings } from '@core/auth/auth.actions'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { getOS } from '@core/utils'; export interface DeviceCheckConnectivityDialogData { deviceId: EntityId; afterAdd: boolean; } + @Component({ selector: 'tb-device-check-connectivity-dialog', templateUrl: './device-check-connectivity-dialog.component.html', @@ -62,7 +65,7 @@ export class DeviceCheckConnectivityDialogComponent extends latestTelemetry: Array = []; - commands: {[key: string]: string}; + commands: PublishTelemetryCommand; allowTransportType = new Set(); selectTransportType: NetworkTransportType; @@ -77,6 +80,45 @@ export class DeviceCheckConnectivityDialogComponent extends notShowAgain = false; + httpTabIndex = 0; + mqttTabIndex = 0; + coapTabIndex = 0; + + readonly installCoap = '```bash\n' + + 'git clone https://github.com/obgm/libcoap --recursive\n' + + '{:copy-code}\n' + + '```\n' + + '
\n' + + '\n' + + '```bash\n' + + 'cd libcoap\n' + + '{:copy-code}\n' + + '```\n' + + '
\n' + + '\n' + + '```bash\n' + + './autogen.sh\n' + + '{:copy-code}\n' + + '```\n' + + '
\n' + + '\n' + + '```bash\n' + + './configure --with-openssl --disable-doxygen --disable-manpages --disable-shared\n' + + '{:copy-code}\n' + + '```\n' + + '
\n' + + '\n' + + '```bash\n' + + 'make\n' + + '{:copy-code}\n' + + '```\n' + + '
\n' + + '\n' + + '```bash\n' + + 'sudo make install\n' + + '{:copy-code}\n' + + '```'; + private telemetrySubscriber: TelemetrySubscriber; private currentTime = Date.now(); @@ -125,11 +167,21 @@ export class DeviceCheckConnectivityDialogComponent extends } } - createMarkDownCommand(command: string): string { + createMarkDownCommand(commands: string | string[]): string { + if (Array.isArray(commands)) { + const formatCommands: Array = []; + commands.forEach(command => formatCommands.push(this.createMarkDownSingleCommand(command))); + return formatCommands.join('
\n'); + } else { + return this.createMarkDownSingleCommand(commands); + } + } + + private createMarkDownSingleCommand(command: string): string { return '```bash\n' + - command + - '{:copy-code}\n' + - '```'; + command + + '{:copy-code}\n' + + '```'; } private loadCommands() { @@ -144,6 +196,7 @@ export class DeviceCheckConnectivityDialogComponent extends } }); this.selectTransportType = this.allowTransportType.values().next().value; + this.selectTabIndexForUserOS(); this.loadedCommand = true; } ); @@ -180,4 +233,28 @@ export class DeviceCheckConnectivityDialogComponent extends }); } + private selectTabIndexForUserOS() { + const currentOS = getOS(); + switch (currentOS) { + case 'linux': + case 'android': + this.httpTabIndex = 2; + this.mqttTabIndex = 2; + this.coapTabIndex = 1; + break; + case 'macos': + case 'ios': + this.httpTabIndex = 1; + this.mqttTabIndex = 1; + break; + case 'windows': + this.httpTabIndex = 0; + this.mqttTabIndex = 0; + break; + default: + this.mqttTabIndex = this.commands.mqtt?.docker ? 3 : 0; + this.coapTabIndex = this.commands.coap?.docker ? 2 : 1; + } + } + } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index b371c131df..e5dd1e9efc 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -837,6 +837,31 @@ export interface ClaimResult { response: ClaimResponse; } +export interface PublishTelemetryCommand { + http?: { + http?: string; + https?: string; + }; + mqtt: { + mqtt?: string; + mqtts?: string | Array; + docker?: { + mqtt?: string; + mqtts?: string | Array; + }; + }; + coap: { + coap?: string; + coaps?: string | Array; + docker?: { + coap?: string; + coaps?: string | Array; + }; + }; + lwm2m?: string; + snmp?: string; +} + export const dayOfWeekTranslations = new Array( 'device-profile.schedule-day.monday', 'device-profile.schedule-day.tuesday', diff --git a/ui-ngx/src/assets/docker.svg b/ui-ngx/src/assets/docker.svg new file mode 100644 index 0000000000..f152739de6 --- /dev/null +++ b/ui-ngx/src/assets/docker.svg @@ -0,0 +1 @@ + diff --git a/ui-ngx/src/assets/help/en_US/device/install_coap_client.md b/ui-ngx/src/assets/help/en_US/device/install_coap_client.md deleted file mode 100644 index 0612acad26..0000000000 --- a/ui-ngx/src/assets/help/en_US/device/install_coap_client.md +++ /dev/null @@ -1,40 +0,0 @@ - #### CoAP installation instructions ---- -
- -Install coap client tool on your **Linux/macOS**: - -```bash -git clone https://github.com/obgm/libcoap --recursive -{:copy-code} -``` -
- -```bash -cd libcoap -{:copy-code} -``` -
- -```bash -./autogen.sh -{:copy-code} -``` -
- -```bash -./configure --with-openssl --disable-doxygen --disable-manpages --disable-shared -{:copy-code} -``` -
- -```bash -make -{:copy-code} -``` -
- -```bash -sudo make install -{:copy-code} -``` diff --git a/ui-ngx/src/assets/help/en_US/device/install_curl.md b/ui-ngx/src/assets/help/en_US/device/install_curl.md deleted file mode 100644 index 0ba60fc590..0000000000 --- a/ui-ngx/src/assets/help/en_US/device/install_curl.md +++ /dev/null @@ -1,34 +0,0 @@ -#### cURL installation instructions ---- -
-
- - Ubuntu - MacOS - Windows - -
- - -

Install cURL tool:

- -
- -

Install cURL tool:

- -
- -
Starting Windows 10 b17063, cURL is available by default.
-
-
-
diff --git a/ui-ngx/src/assets/help/en_US/device/install_mqtt_client.md b/ui-ngx/src/assets/help/en_US/device/install_mqtt_client.md deleted file mode 100644 index 941dce7ab4..0000000000 --- a/ui-ngx/src/assets/help/en_US/device/install_mqtt_client.md +++ /dev/null @@ -1,38 +0,0 @@ - #### MQTT client tool installation instructions ---- -
-
- - Ubuntu - MacOS - Windows - -
- - -

Install mqtt client tool:

- -
- -

Install mqtt client tool:

- -
- -

Install mqtt client tool:

- - descriptionHow to install MQTT Box -
-
-
diff --git a/ui-ngx/src/assets/linux.svg b/ui-ngx/src/assets/linux.svg new file mode 100644 index 0000000000..66f505437f --- /dev/null +++ b/ui-ngx/src/assets/linux.svg @@ -0,0 +1 @@ + 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 3374338761..8e254a655c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -880,7 +880,8 @@ "loading": "Loading...", "proceed": "Proceed", "open-details-page": "Open details page", - "not-found": "Not found" + "not-found": "Not found", + "documentation": "Documentation" }, "content-type": { "json": "Json", @@ -1389,16 +1390,10 @@ "device-created-check-connectivity": "Device created. Let's check connectivity!", "loading-check-connectivity-command": "Loading check connectivity commands...", "use-following-instructions": "Use the following instructions for sending telemetry on behalf of the device using shell", - "install-curl": "Install cURL tool.", - "install-mqtt-client": "Install mgtt client tool.", - "install-coap-cli": "Install coap-cli tool.", - "http-command": "HTTP (Linux, macOS or Windows)", - "https-command": "HTTPS (Linux, macOS or Windows)", - "mqtt-command": "MQTT (Linux, macOS)", - "mqtts-command": "MQTT over SSL (Linux, macOS)", + "execute-following-command": "Executive the following command", + "install-curl-windows": "Starting Windows 10 b17063, cURL is available by default", + "install-necessary-client-tools": "Install necessary client tools", "mqtts-x509-command": "Use the following documentation to connect the device via MQTT with authorization X509", - "coap-command": "CoAP (Linux, macOS)", - "coaps-command": "CoAP over DTLS (Linux, macOS)", "coaps-x509-command": "Use the following documentation to connect the device via CoAP over DTLS with authorization X509", "snmp-command": "Use the following documentation to connect the device through the SNMP.", "lwm2m-command": "Use the following documentation to connect the device through the LWM2M." diff --git a/ui-ngx/src/assets/macos.svg b/ui-ngx/src/assets/macos.svg new file mode 100644 index 0000000000..c3bac982fb --- /dev/null +++ b/ui-ngx/src/assets/macos.svg @@ -0,0 +1 @@ + diff --git a/ui-ngx/src/assets/windows.svg b/ui-ngx/src/assets/windows.svg new file mode 100644 index 0000000000..1f168c099e --- /dev/null +++ b/ui-ngx/src/assets/windows.svg @@ -0,0 +1 @@ + diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index a01c157e4c..a0d09d42c4 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -144,6 +144,13 @@ &.space-between { justify-content: space-between; } + &.no-border { + border: none; + border-radius: 0; + } + &.no-padding { + padding: 0; + } .mat-divider-vertical { height: 56px; margin-top: -7px; From 9d8a9943bf229216099fc1a7c4e29d4e223ab925 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 21 Jul 2023 18:26:14 +0300 Subject: [PATCH 110/168] UI: Add value card widget. Introduce tb-icon component to handle both font and svg (mdi) icons. --- .../json/system/widget_bundles/cards.json | 42 + ui-ngx/src/app/app.component.ts | 47 +- ui-ngx/src/app/core/services/menu.models.ts | 2 - ui-ngx/src/app/core/services/menu.service.ts | 52 +- ui-ngx/src/app/modules/common/modules-map.ts | 2 + .../add-widget-dialog.component.html | 63 +- .../add-widget-dialog.component.scss | 29 + .../add-widget-dialog.component.ts | 7 +- .../dashboard-page/edit-widget.component.html | 4 +- .../entity/entities-table.component.html | 3 +- .../components/event/event-table-config.ts | 1 - .../components/router-tabs.component.html | 3 +- .../components/router-tabs.component.scss | 2 +- .../manage-widget-actions.component.html | 2 +- .../basic/basic-widget-config.module.ts | 8 +- .../simple-card-basic-config.component.ts | 28 +- .../value-card-basic-config.component.html | 59 + .../value-card-basic-config.component.ts | 254 + .../widget-actions-panel.component.html | 2 +- .../widget/config/data-keys.component.html | 2 +- .../widget/config/widget-settings.models.ts | 263 + .../cards/value-card-widget.component.html | 70 + .../cards/value-card-widget.component.scss | 70 + .../lib/cards/value-card-widget.component.ts | 144 + .../lib/cards/value-card-widget.models.ts | 127 + .../add-doc-link-dialog.component.html | 2 +- .../add-quick-link-dialog.component.html | 2 +- .../lib/home-page/doc-link.component.html | 10 +- .../home-page/doc-links-widget.component.html | 6 +- .../edit-links-dialog.component.html | 6 +- .../widget/lib/home-page/home-page.scss | 1 + .../lib/home-page/quick-link.component.html | 19 +- .../quick-links-widget.component.html | 7 +- .../lib/multiple-input-widget.component.html | 8 +- .../lib/navigation-card-widget.component.html | 2 +- .../navigation-cards-widget.component.html | 3 +- .../navigation-cards-widget.component.scss | 2 +- .../lib/rpc/persistent-table.component.html | 6 +- .../color-settings-panel.component.html | 105 + .../color-settings-panel.component.scss | 85 + .../common/color-settings-panel.component.ts | 131 + .../common/color-settings.component.html | 30 + .../common/color-settings.component.ts | 124 + .../common/font-settings-panel.component.html | 95 + .../common/font-settings-panel.component.scss | 51 + .../common/font-settings-panel.component.ts | 136 + .../common/font-settings.component.html | 25 + .../common/font-settings.component.ts | 102 + .../common/image-cards-select.component.html | 43 + .../common/image-cards-select.component.scss | 116 + .../common/image-cards-select.component.ts | 190 + .../lib/settings/widget-settings.module.ts | 26 +- .../widget/widget-component.service.ts | 6 + .../widget/widget-components.module.ts | 7 +- .../widget/widget-container.component.html | 14 +- .../widget/widget-container.component.scss | 2 +- .../widget/widget-container.component.ts | 7 +- .../widget/widget-preview.component.html | 2 + .../widget/widget-preview.component.scss | 9 +- .../widget/widget-preview.component.ts | 6 + .../home/menu/menu-link.component.html | 3 +- .../home/menu/menu-toggle.component.html | 3 +- .../home/models/dashboard-component.models.ts | 3 + .../entity/entities-table-config.models.ts | 1 - .../home/models/widget-component.models.ts | 1 + .../home/pages/admin/admin-routing.module.ts | 3 +- .../home-links/home-links.component.html | 3 +- .../home-links/home-links.component.scss | 2 +- .../select-widget-type-dialog.component.html | 8 +- .../select-widget-type-dialog.component.scss | 2 +- .../components/breadcrumb.component.html | 6 +- .../shared/components/breadcrumb.component.ts | 2 - .../src/app/shared/components/breadcrumb.ts | 1 - .../components/color-input.component.html | 4 +- .../components/color-input.component.scss | 6 - .../app/shared/components/icon.component.ts | 281 + .../material-icon-select.component.html | 8 +- .../material-icon-select.component.scss | 18 - .../components/material-icons.component.html | 4 +- .../notification/notification.component.html | 8 +- .../src/app/shared/components/public-api.ts | 1 + ui-ngx/src/app/shared/models/icon.models.ts | 63 +- ui-ngx/src/app/shared/models/widget.models.ts | 4 +- ui-ngx/src/app/shared/shared.module.ts | 7 +- .../en_US/widget/lib/card/value_color_fn.md | 40 + .../help/en_US/widget/lib/map/color_fn.md | 2 +- .../en_US/widget/lib/map/path_color_fn.md | 2 +- .../widget/lib/map/path_point_color_fn.md | 2 +- .../en_US/widget/lib/map/polygon_color_fn.md | 2 +- .../assets/locale/locale.constant-en_US.json | 25 +- .../src/assets/metadata/material-icons.json | 6368 +---------------- .../widget/value-card/centered-layout.svg | 21 + .../widget/value-card/horizontal-layout.svg | 21 + .../value-card/horizontal-reversed-layout.svg | 21 + .../widget/value-card/simplified-layout.svg | 19 + .../widget/value-card/square-layout.svg | 24 + .../widget/value-card/vertical-layout.svg | 20 + ui-ngx/src/form.scss | 42 +- ui-ngx/src/styles.scss | 26 +- 99 files changed, 3099 insertions(+), 6650 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts create mode 100644 ui-ngx/src/app/shared/components/icon.component.ts create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/card/value_color_fn.md create mode 100644 ui-ngx/src/assets/widget/value-card/centered-layout.svg create mode 100644 ui-ngx/src/assets/widget/value-card/horizontal-layout.svg create mode 100644 ui-ngx/src/assets/widget/value-card/horizontal-reversed-layout.svg create mode 100644 ui-ngx/src/assets/widget/value-card/simplified-layout.svg create mode 100644 ui-ngx/src/assets/widget/value-card/square-layout.svg create mode 100644 ui-ngx/src/assets/widget/value-card/vertical-layout.svg diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index 473d3d215b..cc2c74c359 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -225,6 +225,48 @@ "settingsDirective": "tb-dashboard-state-widget-settings", "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"syncParentStateParams\":true,\"defaultAutofillLayout\":true,\"defaultMargin\":0,\"defaultBackgroundColor\":\"#fff\"},\"title\":\"Dashboard state widget\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"showLegend\":false}" } + }, + { + "alias": "value_card", + "name": "Value card", + "image": null, + "description": "Designed to display single value of the selected attribute or timeseries data. Widget styles are customizable.", + "descriptor": { + "type": "latest", + "sizeX": 2.5, + "sizeY": 2.5, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '250px',\n previewHeight: '250px'\n };\n};\n\nself.onDestroy = function() {\n};\n", + "settingsSchema": "", + "dataKeySettingsSchema": "", + "settingsDirective": "", + "hasBasicMode": true, + "basicModeDirective": "tb-value-card-basic-config", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"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}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}" + } + }, + { + "alias": "horizontal_value_card", + "name": "Horizontal value card", + "image": null, + "description": "Designed to display single value of the selected attribute or timeseries data. Widget styles are customizable.", + "descriptor": { + "type": "latest", + "sizeX": 5, + "sizeY": 1.5, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n horizontal: true,\n previewWidth: '420px',\n previewHeight: '130px'\n };\n};\n\nself.onDestroy = function() {\n};\n", + "settingsSchema": "", + "dataKeySettingsSchema": "", + "settingsDirective": "", + "hasBasicMode": true, + "basicModeDirective": "tb-value-card-basic-config", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"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}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Horizontal value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}" + } } ] } \ No newline at end of file diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index 8f612da5a1..6ad217e20f 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -30,6 +30,7 @@ import { combineLatest } from 'rxjs'; import { selectIsAuthenticated, selectIsUserLoaded } from '@core/auth/auth.selectors'; import { distinctUntilChanged, filter, map, skip } from 'rxjs/operators'; import { AuthService } from '@core/auth/auth.service'; +import { svgIcons } from '@shared/models/icon.models'; @Component({ selector: 'tb-root', @@ -55,44 +56,14 @@ export class AppComponent implements OnInit { } }); - this.matIconRegistry.addSvgIconLiteral( - 'google-logo', - this.domSanitizer.bypassSecurityTrustHtml( - '' - ) - ); - - this.matIconRegistry.addSvgIconLiteral( - 'github-logo', - this.domSanitizer.bypassSecurityTrustHtml( - '' - ) - ); - - this.matIconRegistry.addSvgIconLiteral( - 'facebook-logo', - this.domSanitizer.bypassSecurityTrustHtml( - '' - ) - ); - - this.matIconRegistry.addSvgIconLiteral( - 'apple-logo', - this.domSanitizer.bypassSecurityTrustHtml( - '' - ) - ); - - this.matIconRegistry.addSvgIconLiteral( - 'queues-list', - this.domSanitizer.bypassSecurityTrustHtml( - '' + - '' + - '' + - '' + - '' - ) - ); + for (const svgIcon of Object.keys(svgIcons)) { + this.matIconRegistry.addSvgIconLiteral( + svgIcon, + this.domSanitizer.bypassSecurityTrustHtml( + svgIcons[svgIcon] + ) + ); + } this.storageService.testLocalStorage(); diff --git a/ui-ngx/src/app/core/services/menu.models.ts b/ui-ngx/src/app/core/services/menu.models.ts index 2043eeadea..47072943be 100644 --- a/ui-ngx/src/app/core/services/menu.models.ts +++ b/ui-ngx/src/app/core/services/menu.models.ts @@ -24,7 +24,6 @@ export interface MenuSection extends HasUUID{ type: MenuSectionType; path: string; icon: string; - isMdiIcon?: boolean; pages?: Array; opened?: boolean; disabled?: boolean; @@ -39,6 +38,5 @@ export interface HomeSection { export interface HomeSectionPlace { name: string; icon: string; - isMdiIcon?: boolean; path: string; } diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index b33c552eb1..0de3346af9 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -111,8 +111,7 @@ export class MenuService { name: 'tenant-profile.tenant-profiles', type: 'link', path: '/tenantProfiles', - icon: 'mdi:alpha-t-box', - isMdiIcon: true + icon: 'mdi:alpha-t-box' }, { id: 'resources', @@ -133,8 +132,7 @@ export class MenuService { name: 'resource.resources-library', type: 'link', path: '/resources/resources-library', - icon: 'mdi:rhombus-split', - isMdiIcon: true + icon: 'mdi:rhombus-split' } ] }, @@ -144,7 +142,6 @@ export class MenuService { type: 'link', path: '/notification', icon: 'mdi:message-badge', - isMdiIcon: true, pages: [ { id: 'notification_inbox', @@ -176,8 +173,7 @@ export class MenuService { fullName: 'notification.notification-templates', type: 'link', path: '/notification/templates', - icon: 'mdi:message-draw', - isMdiIcon: true + icon: 'mdi:message-draw' }, { id: 'notification_rules', @@ -185,8 +181,7 @@ export class MenuService { fullName: 'notification.notification-rules', type: 'link', path: '/notification/rules', - icon: 'mdi:message-cog', - isMdiIcon: true + icon: 'mdi:message-cog' } ] }, @@ -218,8 +213,7 @@ export class MenuService { fullName: 'admin.notifications-settings', type: 'link', path: '/settings/notifications', - icon: 'mdi:message-badge', - isMdiIcon: true + icon: 'mdi:message-badge' }, { id: 'queues', @@ -250,16 +244,14 @@ export class MenuService { name: 'admin.2fa.2fa', type: 'link', path: '/security-settings/2fa', - icon: 'mdi:two-factor-authentication', - isMdiIcon: true + icon: 'mdi:two-factor-authentication' }, { id: 'oauth2', name: 'admin.oauth2.oauth2', type: 'link', path: '/security-settings/oauth2', - icon: 'mdi:shield-account', - isMdiIcon: true + icon: 'mdi:shield-account' } ] } @@ -281,7 +273,6 @@ export class MenuService { { name: 'tenant-profile.tenant-profiles', icon: 'mdi:alpha-t-box', - isMdiIcon: true, path: '/tenantProfiles' }, ] @@ -327,7 +318,6 @@ export class MenuService { { name: 'admin.2fa.2fa', icon: 'mdi:two-factor-authentication', - isMdiIcon: true, path: '/settings/2fa' }, { @@ -361,8 +351,7 @@ export class MenuService { name: 'alarm.alarms', type: 'link', path: '/alarms', - icon: 'mdi:alert-outline', - isMdiIcon: true + icon: 'mdi:alert-outline' }, { id: 'dashboards', @@ -413,16 +402,14 @@ export class MenuService { name: 'device-profile.device-profiles', type: 'link', path: '/profiles/deviceProfiles', - icon: 'mdi:alpha-d-box', - isMdiIcon: true + icon: 'mdi:alpha-d-box' }, { id: 'asset_profiles', name: 'asset-profile.asset-profiles', type: 'link', path: '/profiles/assetProfiles', - icon: 'mdi:alpha-a-box', - isMdiIcon: true + icon: 'mdi:alpha-a-box' } ] }, @@ -513,8 +500,7 @@ export class MenuService { name: 'resource.resources-library', type: 'link', path: '/resources/resources-library', - icon: 'mdi:rhombus-split', - isMdiIcon: true + icon: 'mdi:rhombus-split' } ] }, @@ -524,7 +510,6 @@ export class MenuService { type: 'link', path: '/notification', icon: 'mdi:message-badge', - isMdiIcon: true, pages: [ { id: 'notification_inbox', @@ -556,8 +541,7 @@ export class MenuService { fullName: 'notification.notification-templates', type: 'link', path: '/notification/templates', - icon: 'mdi:message-draw', - isMdiIcon: true + icon: 'mdi:message-draw' }, { id: 'notification_rules', @@ -565,8 +549,7 @@ export class MenuService { fullName: 'notification.notification-rules', type: 'link', path: '/notification/rules', - icon: 'mdi:message-cog', - isMdiIcon: true + icon: 'mdi:message-cog' } ] }, @@ -598,8 +581,7 @@ export class MenuService { fullName: 'admin.notifications-settings', type: 'link', path: '/settings/notifications', - icon: 'mdi:message-badge', - isMdiIcon: true + icon: 'mdi:message-badge' }, { id: 'repository_settings', @@ -673,7 +655,6 @@ export class MenuService { { name: 'asset-profile.asset-profiles', icon: 'mdi:alpha-a-box', - isMdiIcon: true, path: '/profiles/assetProfiles' } ] @@ -689,7 +670,6 @@ export class MenuService { { name: 'device-profile.device-profiles', icon: 'mdi:alpha-d-box', - isMdiIcon: true, path: '/profiles/deviceProfiles' }, { @@ -814,8 +794,7 @@ export class MenuService { name: 'alarm.alarms', type: 'link', path: '/alarms', - icon: 'mdi:alert-outline', - isMdiIcon: true + icon: 'mdi:alert-outline' }, { id: 'dashboards', @@ -874,7 +853,6 @@ export class MenuService { type: 'link', path: '/notification', icon: 'mdi:message-badge', - isMdiIcon: true, pages: [ { id: 'notification_inbox', diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index 767633a3ce..eab8bcf5d5 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -182,6 +182,7 @@ import * as ToggleHeaderComponent from '@shared/components/toggle-header.compone import * as ToggleSelectComponent from '@shared/components/toggle-select.component'; import * as UnitInputComponent from '@shared/components/unit-input.component'; import * as MaterialIconsComponent from '@shared/components/material-icons.component'; +import * as TbIconComponent from '@shared/components/icon.component'; import * as AddEntityDialogComponent from '@home/components/entity/add-entity-dialog.component'; import * as EntitiesTableComponent from '@home/components/entity/entities-table.component'; @@ -484,6 +485,7 @@ class ModulesMap implements IModulesMap { '@shared/components/toggle-select.component': ToggleSelectComponent, '@shared/components/unit-input.component': UnitInputComponent, '@shared/components/material-icons.component': MaterialIconsComponent, + '@shared/components/icon.component': TbIconComponent, '@home/components/entity/add-entity-dialog.component': AddEntityDialogComponent, '@home/components/entity/entities-table.component': EntitiesTableComponent, diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html index ad08c0c89e..7a17157b31 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

widget.add

: {{data.widgetInfo.widgetName}} @@ -32,40 +32,37 @@ close
- - -
-
- - - -
- -
-
-
+
+ + + +
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss new file mode 100644 index 0000000000..6c3b90da84 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss @@ -0,0 +1,29 @@ +/** + * 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. + */ +@import '../../../../../scss/constants'; + +.tb-add-widget-dialog { + .mat-mdc-dialog-content { + padding: 0; + position: relative; + } + @media #{$mat-gt-xs} { + width: 1200px; + .mat-mdc-dialog-content { + height: 600px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts index ada22bba94..67c1551d9d 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts @@ -14,12 +14,12 @@ /// limitations under the License. /// -import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { Component, Inject, OnInit, SkipSelf, ViewEncapsulation } from '@angular/core'; import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, FormGroupDirective, NgForm } from '@angular/forms'; +import { FormGroupDirective, NgForm, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@app/shared/components/dialog.component'; import { Widget, WidgetConfigMode, widgetTypesData } from '@shared/models/widget.models'; @@ -41,7 +41,8 @@ export interface AddWidgetDialogData { selector: 'tb-add-widget-dialog', templateUrl: './add-widget-dialog.component.html', providers: [/*{provide: ErrorStateMatcher, useExisting: AddWidgetDialogComponent}*/], - styleUrls: [] + styleUrls: ['./add-widget-dialog.component.scss'], + encapsulation: ViewEncapsulation.None }) export class AddWidgetDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html index db9af1ba06..a1c8ee6ade 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html @@ -60,7 +60,9 @@ [stateController]="stateController" [dashboardTimewindow]="dashboard.configuration.timewindow" [widget]="widget" - [widgetConfig]="widgetFormGroup.get('widgetConfig').value.config"> + [widgetConfig]="widgetFormGroup.get('widgetConfig').value.config" + [previewWidth]="widgetConfig.typeParameters.previewWidth" + [previewHeight]="widgetConfig.typeParameters.previewHeight">
diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html index 65f4bed2ae..aced3e1fd0 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html @@ -86,8 +86,7 @@ matTooltip="{{ actionDescriptor.name }}" matTooltipPosition="above" (click)="actionDescriptor.onAction($event)"> - - {{actionDescriptor.icon}} + {{actionDescriptor.icon}}
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-quick-link-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-quick-link-dialog.component.html index 6eaf4ca205..75d60e046f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-quick-link-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-quick-link-dialog.component.html @@ -22,7 +22,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html index 2d6ea9b3fa..52e6b54519 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html @@ -38,8 +38,8 @@
- - + +
@@ -47,7 +47,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html index 1ccc2e8b24..e4b56e491b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html @@ -23,7 +23,7 @@ matTooltipPosition="above" mat-icon-button (click)="edit()"> - edit + edit
@@ -32,7 +32,7 @@ [href]="docLink.link" target="_blank"> @@ -43,7 +43,7 @@ matTooltip="{{ 'widgets.documentation.add-link' | translate }}" matTooltipPosition="above" (click)="addLink()"> - add + add
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-links-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-links-dialog.component.html index e70e8cd5c2..baddc057c2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-links-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/edit-links-dialog.component.html @@ -22,7 +22,7 @@
@@ -60,7 +60,7 @@ matTooltip="{{ 'action.drag' | translate }}" matTooltipPosition="above" class="tb-drag-handle"> - drag_indicator + drag_indicator
@@ -71,7 +71,7 @@ matTooltip="{{ (mode === 'docs' ? 'widgets.documentation.add-link' : 'widgets.quick-links.add-link') | translate }}" matTooltipPosition="above" (click)="addLink()"> - add + add
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page.scss b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page.scss index a488d77c6e..4adf70d97d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page.scss @@ -23,6 +23,7 @@ letter-spacing: 0.2px; color: rgba(0, 0, 0, 0.76); .mat-icon { + vertical-align: bottom; margin-right: 10px; color: rgba(0, 0, 0, 0.54); font-size: 20px; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-link.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-link.component.html index 0dec41e71d..da1e9977a1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-link.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-link.component.html @@ -25,21 +25,19 @@ (focusin)="onFocus()" required [matAutocomplete]="linkAutocomplete"> - {{ quickLink.icon }} - + {{ quickLink.icon }} - {{ link.icon }} - + {{ link.icon }} @@ -58,8 +56,8 @@
- - + +
@@ -67,8 +65,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-links-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-links-widget.component.html index 95c2818f1c..58d3d98ee3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-links-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/quick-links-widget.component.html @@ -23,7 +23,7 @@ matTooltipPosition="above" mat-icon-button (click)="edit()"> - edit + edit
@@ -32,8 +32,7 @@ [routerLink]="quickLink.path"> @@ -44,7 +43,7 @@ matTooltip="{{ 'widgets.quick-links.add-link' | translate }}" matTooltipPosition="above" (click)="addLink()"> - add + add
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 0a757fd34f..dec2a70864 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -38,7 +38,7 @@ (focus)="key.isFocused = true; focusInputElement($event)" (blur)="key.isFocused = false; inputChanged(source, key)"> - {{key.settings.icon}} + {{key.settings.icon}} icon @@ -63,7 +63,7 @@ (focus)="key.isFocused = true; focusInputElement($event)" (blur)="key.isFocused = false; inputChanged(source, key)"> - {{key.settings.icon}} + {{key.settings.icon}} icon @@ -99,7 +99,7 @@ (blur)="key.isFocused = false; inputChanged(source, key)" /> - {{key.settings.icon}} + {{key.settings.icon}} icon @@ -123,7 +123,7 @@ [labelPosition]="key.settings.slideToggleLabelPosition" (change)="inputChanged(source, key)"> - {{key.settings.icon}} + {{key.settings.icon}} icon diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.html index 0d33c487b7..dadf3317d8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.html @@ -16,6 +16,6 @@ --> - {{settings.icon}} + {{settings.icon}} {{translatedName}} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.html index dbd4d50981..d63a4ef76f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.html @@ -25,8 +25,7 @@ - {{place.icon}} - + {{place.icon}} {{place.name}} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.scss index f3710fbc81..7d41450655 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.scss @@ -55,7 +55,7 @@ display: flex; flex-direction: column; align-items: center; - mat-icon { + .mat-icon { margin: auto; } span.mdc-button__label { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.html index 93b0742930..66f19f717a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.html @@ -80,7 +80,7 @@ matTooltip="{{ actionDescriptor.displayName }}" matTooltipPosition="above" (click)="onActionButtonClick($event, column, actionDescriptor)"> - {{ actionDescriptor.icon }} + {{ actionDescriptor.icon }}
@@ -88,14 +88,14 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html new file mode 100644 index 0000000000..cd8cf1bc06 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html @@ -0,0 +1,105 @@ + +
+
widgets.color.color-settings
+
+ + + {{ colorTypeTranslationsMap.get(type) | translate }} + + +
+
+
widgets.color.color
+ + +
+
+
+
+ +
+
+ +
+
+ + +
+
+ + +
+
widgets.color.value-range
+
+
+
+
+
widgets.color.from
+ + + +
widgets.color.to
+ + + + + +
+ +
+
+
+ +
+
+ +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss new file mode 100644 index 0000000000..0036723b92 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss @@ -0,0 +1,85 @@ +/** + * 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. + */ + +@import '../../../../../../../../scss/constants'; + +.tb-color-settings-panel { + width: 500px; + height: 470px; + display: flex; + flex-direction: column; + gap: 16px; + @media #{$mat-xs} { + width: 90vw; + } + .tb-color-settings-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-color-ranges-panel { + flex: 1; + min-height: 0; + gap: 16px; + display: flex; + flex-direction: column; + } + .tb-color-ranges { + flex: 1; + gap: 12px; + display: flex; + flex-direction: column; + overflow: auto; + } + .tb-form-row { + height: auto; + .tb-value-range-text { + width: 64px; + font-size: 14px; + color: rgba(0, 0, 0, 0.38); + @media #{$mat-xs} { + width: auto; + } + &.tb-value-range-text-to { + text-align: center; + } + } + } + button.mat-mdc-button-base.tb-add-color-range { + &:not(:disabled) { + color: rgba(0, 0, 0, 0.54); + } + &:disabled { + color: rgba(0, 0, 0, 0.12); + } + } + .tb-color-settings-panel-body { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + } + .tb-color-settings-panel-buttons { + height: 40px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts new file mode 100644 index 0000000000..c20abd8d84 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts @@ -0,0 +1,131 @@ +/// +/// 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. +/// + +import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { + ColorRange, + ColorSettings, + ColorType, + colorTypeTranslations +} from '@home/components/widget/config/widget-settings.models'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { + AbstractControl, + FormControl, + FormGroup, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormGroup +} from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Datasource, DatasourceType } from '@shared/models/widget.models'; +import { deepClone } from '@core/utils'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { WidgetService } from '@core/http/widget.service'; + +@Component({ + selector: 'tb-color-settings-panel', + templateUrl: './color-settings-panel.component.html', + providers: [], + styleUrls: ['./color-settings-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class ColorSettingsPanelComponent extends PageComponent implements OnInit { + + @Input() + colorSettings: ColorSettings; + + @Input() + popover: TbPopoverComponent; + + @Output() + colorSettingsApplied = new EventEmitter(); + + colorType = ColorType; + + colorTypes = Object.keys(ColorType) as ColorType[]; + + colorTypeTranslationsMap = colorTypeTranslations; + + colorSettingsFormGroup: UntypedFormGroup; + + functionScopeVariables = this.widgetService.getWidgetScopeVariables(); + + constructor(private fb: UntypedFormBuilder, + private widgetService: WidgetService, + protected store: Store) { + super(store); + } + + ngOnInit(): void { + this.colorSettingsFormGroup = this.fb.group( + { + type: [this.colorSettings?.type, []], + color: [this.colorSettings?.color, []], + rangeList: this.fb.array((this.colorSettings?.rangeList || []).map(r => this.colorRangeControl(r))), + colorFunction: [this.colorSettings?.colorFunction, []] + } + ); + this.colorSettingsFormGroup.get('type').valueChanges.subscribe(() => { + setTimeout(() => {this.popover?.updatePosition();}, 0); + }); + } + + private colorRangeControl(range: ColorRange): AbstractControl { + return this.fb.group({ + from: [range?.from, []], + to: [range?.to, []], + color: [range?.color, []] + }); + } + + get rangeListFormArray(): UntypedFormArray { + return this.colorSettingsFormGroup.get('rangeList') as UntypedFormArray; + } + + get rangeListFormGroups(): FormGroup[] { + return this.rangeListFormArray.controls as FormGroup[]; + } + + trackByRange(index: number, rangeControl: AbstractControl): any { + return rangeControl; + } + + removeRange(index: number) { + this.rangeListFormArray.removeAt(index); + setTimeout(() => {this.popover?.updatePosition();}, 0); + } + + addRange() { + const newRange: ColorRange = { + color: 'rgba(0,0,0,0.87)' + }; + this.rangeListFormArray.push(this.colorRangeControl(newRange), {emitEvent: true}); + setTimeout(() => {this.popover?.updatePosition();}, 0); + } + + cancel() { + this.popover?.hide(); + } + + applyColorSettings() { + const colorSettings = this.colorSettingsFormGroup.value; + this.colorSettingsApplied.emit(colorSettings); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.html new file mode 100644 index 0000000000..3c058113e1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.html @@ -0,0 +1,30 @@ + + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts new file mode 100644 index 0000000000..bffad41080 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts @@ -0,0 +1,124 @@ +/// +/// 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. +/// + +import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ColorSettings, ColorType, ComponentStyle } from '@home/components/widget/config/widget-settings.models'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { + ColorSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/color-settings-panel.component'; + +@Component({ + selector: 'tb-color-settings', + templateUrl: './color-settings.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ColorSettingsComponent), + multi: true + } + ] +}) +export class ColorSettingsComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + colorType = ColorType; + + modelValue: ColorSettings; + + colorStyle: ComponentStyle = {}; + + private propagateChange = null; + + constructor(private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef) {} + + ngOnInit(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + this.updateColorStyle(); + } + + writeValue(value: ColorSettings): void { + this.modelValue = value; + this.updateColorStyle(); + } + + openColorSettingsPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + colorSettings: this.modelValue + }; + const colorSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, ColorSettingsPanelComponent, 'left', true, null, + ctx, + {}, + {}, {}, true); + colorSettingsPanelPopover.tbComponentRef.instance.popover = colorSettingsPanelPopover; + colorSettingsPanelPopover.tbComponentRef.instance.colorSettingsApplied.subscribe((colorSettings) => { + colorSettingsPanelPopover.hide(); + this.modelValue = colorSettings; + this.updateColorStyle(); + this.propagateChange(this.modelValue); + }); + } + } + + private updateColorStyle() { + if (!this.disabled) { + let colors: string[] = [this.modelValue.color]; + if (this.modelValue.type === ColorType.range && this.modelValue.rangeList?.length) { + const rangeColors = this.modelValue.rangeList.slice(0, Math.min(2, this.modelValue.rangeList.length)).map(r => r.color); + colors = colors.concat(rangeColors); + } + if (colors.length === 1) { + this.colorStyle = {backgroundColor: colors[0]}; + } else { + const gradientValues: string[] = []; + const step = 100 / colors.length; + for (let i = 0; i < colors.length; i++) { + gradientValues.push(`${colors[i]} ${step*i}%`); + gradientValues.push(`${colors[i]} ${step*(i+1)}%`); + } + this.colorStyle = {background: `linear-gradient(90deg, ${gradientValues.join(', ')})`}; + } + } else { + this.colorStyle = {}; + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html new file mode 100644 index 0000000000..8cf2e4e9e3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html @@ -0,0 +1,95 @@ + +
+
widgets.widget-font.font-settings
+
+
widgets.widget-font.size
+
+ + + + + + {{ cssUnit }} + + +
+
+
+
widgets.widget-font.font-family
+ + + + + + + + + +
+
+
widgets.widget-font.font-weight
+ + + + {{ fontWeightTranslationsMap.has(weight) ? (fontWeightTranslationsMap.get(weight) | translate) : weight }} + + + +
+
+
widgets.widget-font.font-style
+ + + + {{ fontStyleTranslationsMap.get(style) | translate }} + + + +
+ +
+
widgets.widget-font.preview
+
{{ previewText }}
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.scss new file mode 100644 index 0000000000..3475a14e8c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.scss @@ -0,0 +1,51 @@ +/** + * 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. + */ +.tb-font-settings-panel { + width: 100%; + display: flex; + flex-direction: column; + gap: 16px; + .tb-font-settings-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-form-row { + .fixed-title-width { + min-width: 120px; + } + &.font-preview { + align-items: flex-start; + .preview-text { + max-height: 300px; + max-width: 400px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + } + .tb-font-settings-panel-buttons { + height: 60px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts new file mode 100644 index 0000000000..9369167746 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts @@ -0,0 +1,136 @@ +/// +/// 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. +/// + +import { + Component, + ElementRef, + EventEmitter, + Input, + OnInit, + Output, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { + commonFonts, + ComponentStyle, + cssUnits, + Font, + fontStyles, + fontStyleTranslations, + fontWeights, + fontWeightTranslations, + textStyle +} from '@home/components/widget/config/widget-settings.models'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Observable } from 'rxjs'; +import { map, startWith, tap } from 'rxjs/operators'; + +@Component({ + selector: 'tb-font-settings-panel', + templateUrl: './font-settings-panel.component.html', + providers: [], + styleUrls: ['./font-settings-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class FontSettingsPanelComponent extends PageComponent implements OnInit { + + @Input() + font: Font; + + @Input() + previewText = 'AaBbCcDd'; + + @Input() + popover: TbPopoverComponent; + + @Output() + fontApplied = new EventEmitter(); + + @ViewChild('familyInput', {static: true}) familyInput: ElementRef; + + cssUnitsList = cssUnits; + + fontWeightsList = fontWeights; + + fontWeightTranslationsMap = fontWeightTranslations; + + fontStylesList = fontStyles; + + fontStyleTranslationsMap = fontStyleTranslations; + + fontFormGroup: UntypedFormGroup; + + filteredFontFamilies: Observable>; + + familySearchText = ''; + + previewStyle: ComponentStyle = {}; + + constructor(private fb: UntypedFormBuilder, + protected store: Store) { + super(store); + } + + ngOnInit(): void { + this.fontFormGroup = this.fb.group( + { + size: [this.font?.size, [Validators.required, Validators.min(0)]], + sizeUnit: [this.font?.sizeUnit, [Validators.required]], + family: [this.font?.family, [Validators.required]], + weight: [this.font?.weight, [Validators.required]], + style: [this.font?.style, [Validators.required]] + } + ); + if (this.font) { + this.previewStyle = textStyle(this.font, '1'); + } + this.fontFormGroup.valueChanges.subscribe((value: Font) => { + if (this.fontFormGroup.valid) { + this.previewStyle = textStyle(value, '1'); + setTimeout(() => {this.popover?.updatePosition();}, 0); + } + }); + this.filteredFontFamilies = this.fontFormGroup.get('family').valueChanges + .pipe( + startWith(''), + tap((searchText) => { this.familySearchText = searchText || ''; }), + map(() => commonFonts.filter(f => f.toUpperCase().includes(this.familySearchText.toUpperCase()))) + ); + } + + clearFamily() { + this.fontFormGroup.get('family').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.familyInput.nativeElement.blur(); + this.familyInput.nativeElement.focus(); + }, 0); + } + + cancel() { + this.popover?.hide(); + } + + applyFont() { + const font = this.fontFormGroup.value; + this.fontApplied.emit(font); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.html new file mode 100644 index 0000000000..574ce98c1d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.html @@ -0,0 +1,25 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts new file mode 100644 index 0000000000..fdeccb1f15 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts @@ -0,0 +1,102 @@ +/// +/// 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. +/// + +import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Font } from '@home/components/widget/config/widget-settings.models'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { FontSettingsPanelComponent } from '@home/components/widget/lib/settings/common/font-settings-panel.component'; +import { isDefinedAndNotNull } from '@core/utils'; + +@Component({ + selector: 'tb-font-settings', + templateUrl: './font-settings.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FontSettingsComponent), + multi: true + } + ] +}) +export class FontSettingsComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + @Input() + previewText: string | (() => string); + + private modelValue: Font; + + private propagateChange = null; + + constructor(private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef) {} + + ngOnInit(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: Font): void { + this.modelValue = value; + } + + openFontSettingsPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + font: this.modelValue + }; + if (isDefinedAndNotNull(this.previewText)) { + const previewText = typeof this.previewText === 'string' ? this.previewText : this.previewText(); + if (previewText) { + ctx.previewText = previewText; + } + } + const fontSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, FontSettingsPanelComponent, 'left', true, null, + ctx, + {}, + {}, {}, true); + fontSettingsPanelPopover.tbComponentRef.instance.popover = fontSettingsPanelPopover; + fontSettingsPanelPopover.tbComponentRef.instance.fontApplied.subscribe((font) => { + fontSettingsPanelPopover.hide(); + this.modelValue = font; + this.propagateChange(this.modelValue); + }); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.html new file mode 100644 index 0000000000..eb2bbd6711 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.html @@ -0,0 +1,43 @@ + +
+ + +
+
{{ label }}
+ + + {{ expanded ? 'expand_less' : 'expand_more' }} + +
+
+ + + +
+
+
{{ option.name }}
+
+ +
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.scss new file mode 100644 index 0000000000..37c06ed239 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.scss @@ -0,0 +1,116 @@ +/** + * 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. + */ +.tb-image-cards-select.tb-form-panel { + .tb-form-row { + transition: all .3s; + &.expanded { + padding: 11px 7px 11px 16px; + } + } + .tb-image-cards-value-field { + cursor: pointer; + user-select: none; + input { + cursor: pointer; + pointer-events: none; + } + } + .mat-expansion-panel { + &.tb-settings { + > .mat-expansion-panel-header { + height: auto; + .mat-content { + margin: 0; + } + .tb-form-row { + font-weight: normal; + font-size: 16px; + color: rgba(0, 0, 0, 0.87); + } + .mat-expansion-indicator { + display: none; + } + } + > .mat-expansion-panel-content { + > .mat-expansion-panel-body { + padding: 0 16px 16px !important; + } + } + } + } + .tb-image-cards-option { + width: 100%; + height: 100%; + cursor: pointer; + padding: 8px 12px 12px 12px; + display: flex; + flex-direction: column; + gap: 8px; + align-items: start; + position: relative; + .tb-image-cards-option-background { + border-radius: 4px; + position: absolute; + top: 1px; + left: 1px; + right: 1px; + bottom: 1px; + background: rgba(0, 0, 0, 0.04); + } + &:before { + content: unset; + border-radius: 4px; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + } + .tb-image-cards-option-title { + z-index: 1; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.54); + } + .tb-image-cards-option-image-container { + z-index: 1; + flex: 1; + width: 100%; + min-height: 0; + display: flex; + justify-content: center; + } + &.selected { + .tb-image-cards-option-background { + background: #305680; + opacity: 0.04; + } + &:before { + content: ""; + border: 1px solid #305680; + opacity: 0.32; + } + .tb-image-cards-option-title { + font-size: 13px; + font-weight: 500; + color: #305680; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts new file mode 100644 index 0000000000..e538653703 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts @@ -0,0 +1,190 @@ +/// +/// 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. +/// + +import { + AfterContentInit, + Component, + ContentChildren, + Directive, + ElementRef, + forwardRef, + Input, + OnDestroy, OnInit, + QueryList, + ViewEncapsulation +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { Observable, Subject } from 'rxjs'; +import { map, share, startWith, takeUntil } from 'rxjs/operators'; +import { BreakpointObserver } from '@angular/cdk/layout'; +import { MediaBreakpoints } from '@shared/models/constants'; + +export interface ImageCardsSelectOption { + name: string; + value: any; + image: string; +} + +@Directive( + { + // eslint-disable-next-line @angular-eslint/directive-selector + selector: 'tb-image-cards-select-option', + } +) +export class ImageCardsSelectOptionDirective { + + @Input() value: any; + + @Input() image: string; + + get viewValue(): string { + return (this._element?.nativeElement.textContent || '').trim(); + } + + constructor( + private _element: ElementRef + ) {} +} + +@Component({ + selector: 'tb-image-cards-select', + templateUrl: './image-cards-select.component.html', + styleUrls: ['./image-cards-select.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ImageCardsSelectComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit, AfterContentInit, OnDestroy { + + @ContentChildren(ImageCardsSelectOptionDirective) imageCardsSelectOptions: QueryList; + + @Input() + @coerceBoolean() + disabled: boolean; + + @Input() + cols = 4; + + @Input() + colsLtMd = 2; + + @Input() + rowHeight = '9:5'; + + @Input() + label: string; + + valueFormControl: UntypedFormControl; + + options: ImageCardsSelectOption[] = []; + + modelValue: any; + + expanded = false; + + cols$: Observable; + + private propagateChange = null; + + private _destroyed = new Subject(); + + constructor(private breakpointObserver: BreakpointObserver) { + this.valueFormControl = new UntypedFormControl(''); + } + + ngOnInit(): void { + const gridColumns = this.breakpointObserver.isMatched(MediaBreakpoints['lt-md']) ? this.colsLtMd : this.cols; + this.cols$ = this.breakpointObserver + .observe(MediaBreakpoints['lt-md']).pipe( + map((state) => state.matches ? this.colsLtMd : this.cols), + startWith(gridColumns), + share() + ); + } + + ngAfterContentInit(): void { + this.imageCardsSelectOptions.changes.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() => { + this.syncImageCardsSelectOptions(); + }); + } + + ngOnDestroy() { + this._destroyed.next(); + this._destroyed.complete(); + } + + private syncImageCardsSelectOptions() { + if (this.imageCardsSelectOptions?.length) { + this.options.length = 0; + this.imageCardsSelectOptions.forEach(option => { + this.options.push( + { name: option.viewValue, + value: option.value, + image: option.image + } + ); + }); + this.updateDisplayValue(); + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.valueFormControl.disable(); + } else { + this.valueFormControl.enable(); + } + } + + writeValue(value: any): void { + this.modelValue = value; + this.updateDisplayValue(); + } + + updateModel(value: any) { + this.modelValue = value; + this.updateDisplayValue(); + this.propagateChange(this.modelValue); + this.expanded = false; + } + + toggleSelectPanel($event: Event) { + $event.stopPropagation(); + if (!this.disabled) { + this.expanded = !this.expanded; + } + } + + private updateDisplayValue() { + const currentOption = this.options.find(o => o.value === this.modelValue); + const displayValue = currentOption ? currentOption.name : ''; + this.valueFormControl.patchValue(displayValue, {emitEvent: 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 2dae32a922..1a9834dd91 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 @@ -266,6 +266,16 @@ import { QuickLinksWidgetSettingsComponent } from '@home/components/widget/lib/settings/home-page/quick-links-widget-settings.component'; import { LegendConfigComponent } from '@home/components/widget/lib/settings/common/legend-config.component'; +import { + ImageCardsSelectOptionDirective, + ImageCardsSelectComponent +} from '@home/components/widget/lib/settings/common/image-cards-select.component'; +import { FontSettingsComponent } from '@home/components/widget/lib/settings/common/font-settings.component'; +import { FontSettingsPanelComponent } from '@home/components/widget/lib/settings/common/font-settings-panel.component'; +import { ColorSettingsComponent } from '@home/components/widget/lib/settings/common/color-settings.component'; +import { + ColorSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/color-settings-panel.component'; @NgModule({ declarations: [ @@ -367,7 +377,13 @@ import { LegendConfigComponent } from '@home/components/widget/lib/settings/comm RouteMapWidgetSettingsComponent, TripAnimationWidgetSettingsComponent, DocLinksWidgetSettingsComponent, - QuickLinksWidgetSettingsComponent + QuickLinksWidgetSettingsComponent, + ImageCardsSelectOptionDirective, + ImageCardsSelectComponent, + FontSettingsComponent, + FontSettingsPanelComponent, + ColorSettingsComponent, + ColorSettingsPanelComponent ], imports: [ CommonModule, @@ -473,7 +489,13 @@ import { LegendConfigComponent } from '@home/components/widget/lib/settings/comm RouteMapWidgetSettingsComponent, TripAnimationWidgetSettingsComponent, DocLinksWidgetSettingsComponent, - QuickLinksWidgetSettingsComponent + QuickLinksWidgetSettingsComponent, + ImageCardsSelectOptionDirective, + ImageCardsSelectComponent, + FontSettingsComponent, + FontSettingsPanelComponent, + ColorSettingsComponent, + ColorSettingsPanelComponent ] }) export class WidgetSettingsModule { diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index e3ff270197..b622aeaa83 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -540,6 +540,12 @@ export class WidgetComponentService { if (isUndefined(result.typeParameters.processNoDataByWidget)) { result.typeParameters.processNoDataByWidget = false; } + if (isUndefined(result.typeParameters.previewWidth)) { + result.typeParameters.previewWidth = '100%'; + } + if (isUndefined(result.typeParameters.previewHeight)) { + result.typeParameters.previewHeight = '70%'; + } if (isFunction(widgetTypeInstance.actionSources)) { result.actionSources = widgetTypeInstance.actionSources(); } else { 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 ce2e7055cc..9b53bd96a4 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 @@ -43,6 +43,7 @@ import { HomePageWidgetsModule } from '@home/components/widget/lib/home-page/hom import { WIDGET_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens'; import { FlotWidgetComponent } from '@home/components/widget/lib/flot-widget.component'; import { LegendComponent } from '@home/components/widget/lib/legend.component'; +import { ValueCardWidgetComponent } from '@home/components/widget/lib/cards/value-card-widget.component'; @NgModule({ declarations: @@ -66,7 +67,8 @@ import { LegendComponent } from '@home/components/widget/lib/legend.component'; MarkdownWidgetComponent, SelectEntityDialogComponent, LegendComponent, - FlotWidgetComponent + FlotWidgetComponent, + ValueCardWidgetComponent ], imports: [ CommonModule, @@ -94,7 +96,8 @@ import { LegendComponent } from '@home/components/widget/lib/legend.component'; QrCodeWidgetComponent, MarkdownWidgetComponent, LegendComponent, - FlotWidgetComponent + FlotWidgetComponent, + ValueCardWidgetComponent ], providers: [ {provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html index 23082a121b..6601aa96db 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html @@ -41,7 +41,7 @@ matTooltipClass="tb-tooltip-multiline" matTooltipPosition="above" class="mat-subtitle-1 title"> - {{widget.titleIcon}} + {{widget.titleIcon}} {{widget.customTranslatedTitle}} - {{ action.icon }} + {{ action.icon }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss index 4c7264ce29..a364189372 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss @@ -82,7 +82,7 @@ div.tb-widget { margin: 0 !important; line-height: 20px; - mat-icon { + .mat-icon { width: 20px; min-width: 20px; height: 20px; diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts index bf7042d233..c022015c8f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts @@ -15,6 +15,7 @@ /// import { + AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, @@ -62,7 +63,7 @@ export class WidgetComponentAction { encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) -export class WidgetContainerComponent extends PageComponent implements OnInit, OnDestroy { +export class WidgetContainerComponent extends PageComponent implements OnInit, AfterViewInit, OnDestroy { @HostBinding('class') widgetContainerClass = 'tb-widget-container'; @@ -131,6 +132,10 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O } } + ngAfterViewInit(): void { + this.widget.widgetContext.$widgetElement = $(this.tbWidgetElement.nativeElement); + } + ngOnDestroy(): void { if (this.cssClass) { const el = this.document.getElementById(this.cssClass); diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html index 9b6c683953..74ba76bae1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html @@ -16,6 +16,8 @@ --> ) { diff --git a/ui-ngx/src/app/modules/home/menu/menu-link.component.html b/ui-ngx/src/app/modules/home/menu/menu-link.component.html index ab5b1faa27..0efc34a31c 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-link.component.html +++ b/ui-ngx/src/app/modules/home/menu/menu-link.component.html @@ -16,7 +16,6 @@ --> - {{section.icon}} - + {{section.icon}} {{section.name | translate}} diff --git a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html index de4f16b163..448c67a513 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html +++ b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html @@ -16,8 +16,7 @@ --> - {{section.icon}} - + {{section.icon}} {{section.name | translate}} diff --git a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts index e9787260bb..5f994e4fdc 100644 --- a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts @@ -446,7 +446,10 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { this.titleIconStyle.color = this.widget.config.iconColor; } if (this.widget.config.iconSize) { + this.titleIconStyle.width = this.widget.config.iconSize; + this.titleIconStyle.height = this.widget.config.iconSize; this.titleIconStyle.fontSize = this.widget.config.iconSize; + this.titleIconStyle.lineHeight = this.widget.config.iconSize; } this.dropShadow = isDefined(this.widget.config.dropShadow) ? this.widget.config.dropShadow : true; diff --git a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts index e9fb01c54a..bcd8e793b3 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts @@ -75,7 +75,6 @@ export interface GroupActionDescriptor> { export interface HeaderActionDescriptor { name: string; icon: string; - isMdiIcon?: boolean; isEnabled: () => boolean; onAction: ($event: MouseEvent) => void; } 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 c00d0a8e82..b16e880e02 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 @@ -243,6 +243,7 @@ export class WidgetContext { formatValue }; + $widgetElement: JQuery; $container: JQuery; $containerParent: JQuery; width: number; 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 adc87a7886..5748cc63a6 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 @@ -319,8 +319,7 @@ const routes: Routes = [ title: 'admin.2fa.2fa', breadcrumb: { label: 'admin.2fa.2fa', - icon: 'mdi:two-factor-authentication', - isMdiIcon: true + icon: 'mdi:two-factor-authentication' } } }, diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html index 23619e2632..521f54f53d 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html @@ -27,8 +27,7 @@ - {{place.icon}} - + {{place.icon}} {{place.name}} diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss index a30605652b..9acc1490ba 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss @@ -55,7 +55,7 @@ display: flex; flex-direction: column; align-items: center; - mat-icon { + .mat-icon { margin: auto; } span.mdc-button__label { diff --git a/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.html b/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.html index d15e8fbe1f..531d9d3cfe 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.html @@ -34,13 +34,9 @@
diff --git a/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.scss index 7de71586fa..d289574f15 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.scss @@ -21,7 +21,7 @@ display: flex; flex-direction: column; align-items: center; - mat-icon { + .mat-icon { margin: auto; } span.mdc-button__label { diff --git a/ui-ngx/src/app/shared/components/breadcrumb.component.html b/ui-ngx/src/app/shared/components/breadcrumb.component.html index be7f1e6b16..594578465f 100644 --- a/ui-ngx/src/app/shared/components/breadcrumb.component.html +++ b/ui-ngx/src/app/shared/components/breadcrumb.component.html @@ -37,11 +37,9 @@
- - - + {{ breadcrumb.icon }} - + {{ breadcrumb.ignoreTranslate ? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : utils.customTranslation(breadcrumb.label, breadcrumb.label)) : (breadcrumb.label | translate) }} diff --git a/ui-ngx/src/app/shared/components/breadcrumb.component.ts b/ui-ngx/src/app/shared/components/breadcrumb.component.ts index 2542f58871..380ddc59ce 100644 --- a/ui-ngx/src/app/shared/components/breadcrumb.component.ts +++ b/ui-ngx/src/app/shared/components/breadcrumb.component.ts @@ -117,7 +117,6 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { ignoreTranslate = false; } const icon = breadcrumbConfig.icon || 'home'; - const isMdiIcon = icon.startsWith('mdi:'); const link = [ route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/') ]; const breadcrumb = { id: guid(), @@ -125,7 +124,6 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { labelFunction, ignoreTranslate, icon, - isMdiIcon, link, queryParams: null }; diff --git a/ui-ngx/src/app/shared/components/breadcrumb.ts b/ui-ngx/src/app/shared/components/breadcrumb.ts index 599f8b85e8..77a832dbed 100644 --- a/ui-ngx/src/app/shared/components/breadcrumb.ts +++ b/ui-ngx/src/app/shared/components/breadcrumb.ts @@ -23,7 +23,6 @@ export interface BreadCrumb extends HasUUID{ labelFunction?: () => string; ignoreTranslate: boolean; icon: string; - isMdiIcon: boolean; link: any[]; queryParams: Params; } diff --git a/ui-ngx/src/app/shared/components/color-input.component.html b/ui-ngx/src/app/shared/components/color-input.component.html index c027104c6e..68ec5854f2 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.html +++ b/ui-ngx/src/app/shared/components/color-input.component.html @@ -37,12 +37,12 @@ diff --git a/ui-ngx/src/app/shared/components/color-input.component.scss b/ui-ngx/src/app/shared/components/color-input.component.scss index b81ac0fdf8..2660c16dc8 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.scss +++ b/ui-ngx/src/app/shared/components/color-input.component.scss @@ -32,10 +32,4 @@ margin: 0; } } - button.mat-mdc-button-base.color-box { - width: 40px; - min-width: 40px; - height: 40px; - padding: 7px; - } } diff --git a/ui-ngx/src/app/shared/components/icon.component.ts b/ui-ngx/src/app/shared/components/icon.component.ts new file mode 100644 index 0000000000..d1e2c6ddcd --- /dev/null +++ b/ui-ngx/src/app/shared/components/icon.component.ts @@ -0,0 +1,281 @@ +/// +/// 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. +/// + +import { CanColor, mixinColor } from '@angular/material/core'; +import { + AfterContentInit, + AfterViewChecked, + ChangeDetectionStrategy, + Component, + ElementRef, + ErrorHandler, + Inject, + OnDestroy, + Renderer2, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { MAT_ICON_LOCATION, MatIconLocation, MatIconRegistry } from '@angular/material/icon'; +import { Subscription } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { isSvgIcon, splitIconName } from '@shared/models/icon.models'; +import { ContentObserver } from '@angular/cdk/observers'; + +const _TbIconBase = mixinColor( + class { + constructor(public _elementRef: ElementRef) {} + }, +); + +const funcIriAttributes = [ + 'clip-path', + 'color-profile', + 'src', + 'cursor', + 'fill', + 'filter', + 'marker', + 'marker-start', + 'marker-mid', + 'marker-end', + 'mask', + 'stroke', +]; + +const funcIriAttributeSelector = funcIriAttributes.map(attr => `[${attr}]`).join(', '); + +const funcIriPattern = /^url\(['"]?#(.*?)['"]?\)$/; + +@Component({ + template: '', + selector: 'tb-icon', + exportAs: 'tbIcon', + styleUrls: [], + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color'], + // eslint-disable-next-line @angular-eslint/no-host-metadata-property + host: { + role: 'img', + class: 'mat-icon notranslate', + '[attr.data-mat-icon-type]': '!_useSvgIcon ? "font" : "svg"', + '[attr.data-mat-icon-name]': '_svgName', + '[attr.data-mat-icon-namespace]': '_svgNamespace', + '[class.mat-icon-no-color]': 'color !== "primary" && color !== "accent" && color !== "warn"', + }, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TbIconComponent extends _TbIconBase + implements AfterContentInit, AfterViewChecked, CanColor, OnDestroy { + + @ViewChild('iconNameContent', {static: true}) + _iconNameContent: ElementRef; + + private icon: string; + + get viewValue(): string { + return (this._iconNameContent?.nativeElement.textContent || '').trim(); + } + + private _contentChanges: Subscription = null; + private _previousFontSetClass: string[] = []; + + _useSvgIcon = false; + _svgName: string | null; + _svgNamespace: string | null; + + private _textElement = null; + + private _previousPath?: string; + + private _elementsWithExternalReferences?: Map; + + private _currentIconFetch = Subscription.EMPTY; + + constructor(elementRef: ElementRef, + private contentObserver: ContentObserver, + private renderer: Renderer2, + private _iconRegistry: MatIconRegistry, + @Inject(MAT_ICON_LOCATION) private _location: MatIconLocation, + private readonly _errorHandler: ErrorHandler) { + super(elementRef); + } + + ngAfterContentInit(): void { + this.icon = this.viewValue; + this._updateIcon(); + this._contentChanges = this.contentObserver.observe(this._iconNameContent.nativeElement) + .subscribe(() => { + const content = this.viewValue; + if (content && this.icon !== content) { + this.icon = content; + this._updateIcon(); + } + }); + } + + ngAfterViewChecked() { + const cachedElements = this._elementsWithExternalReferences; + if (cachedElements && cachedElements.size) { + const newPath = this._location.getPathname(); + if (newPath !== this._previousPath) { + this._previousPath = newPath; + this._prependPathToReferences(newPath); + } + } + } + + ngOnDestroy() { + this._contentChanges.unsubscribe(); + this._currentIconFetch.unsubscribe(); + if (this._elementsWithExternalReferences) { + this._elementsWithExternalReferences.clear(); + } + } + + private _updateIcon() { + const useSvgIcon = isSvgIcon(this.icon); + if (this._useSvgIcon !== useSvgIcon) { + this._useSvgIcon = useSvgIcon; + if (!this._useSvgIcon) { + this._updateSvgIcon(undefined); + } else { + this._updateFontIcon(undefined); + } + } + if (this._useSvgIcon) { + this._updateSvgIcon(this.icon); + } else { + this._updateFontIcon(this.icon); + } + } + + private _updateFontIcon(rawName: string | undefined) { + if (rawName) { + this._clearFontIcon(); + const iconName = splitIconName(rawName)[1]; + this._textElement = this.renderer.createText(iconName); + const elem: HTMLElement = this._elementRef.nativeElement; + this.renderer.insertBefore(elem, this._textElement, this._iconNameContent.nativeElement); + const fontSetClasses = ( + this._iconRegistry.getDefaultFontSetClass() + ).filter(className => className.length > 0); + fontSetClasses.forEach(className => elem.classList.add(className)); + this._previousFontSetClass = fontSetClasses; + } else { + this._clearFontIcon(); + } + } + + private _clearFontIcon() { + const elem: HTMLElement = this._elementRef.nativeElement; + if (this._textElement !== null) { + this.renderer.removeChild(elem, this._textElement); + this._textElement = null; + } + this._previousFontSetClass.forEach(className => elem.classList.remove(className)); + this._previousFontSetClass = []; + } + + private _updateSvgIcon(rawName: string | undefined) { + this._svgNamespace = null; + this._svgName = null; + this._currentIconFetch.unsubscribe(); + + if (rawName) { + const [namespace, iconName] = splitIconName(rawName); + if (namespace) { + this._svgNamespace = namespace; + } + if (iconName) { + this._svgName = iconName; + } + this._iconRegistry.getDefaultFontSetClass(); + this._currentIconFetch = this._iconRegistry + .getNamedSvgIcon(iconName, namespace) + .pipe(take(1)) + .subscribe({ + next: (svg) => this._setSvgElement(svg), + error: (err: Error) => { + const errorMessage = `Error retrieving icon ${namespace}:${iconName}! ${err.message}`; + this._errorHandler.handleError(new Error(errorMessage)); + } + }); + } else { + this._clearSvgElement(); + } + } + + private _setSvgElement(svg: SVGElement) { + this._clearSvgElement(); + const path = this._location.getPathname(); + this._previousPath = path; + this._cacheChildrenWithExternalReferences(svg); + this._prependPathToReferences(path); + this.renderer.insertBefore(this._elementRef.nativeElement, svg, this._iconNameContent.nativeElement); + } + + private _clearSvgElement() { + const layoutElement: HTMLElement = this._elementRef.nativeElement; + let childCount = layoutElement.childNodes.length; + if (this._elementsWithExternalReferences) { + this._elementsWithExternalReferences.clear(); + } + while (childCount--) { + const child = layoutElement.childNodes[childCount]; + if (child.nodeType !== 1 || child.nodeName.toLowerCase() === 'svg') { + child.remove(); + } + } + } + + private _cacheChildrenWithExternalReferences(element: SVGElement) { + const elementsWithFuncIri = element.querySelectorAll(funcIriAttributeSelector); + const elements = (this._elementsWithExternalReferences = this._elementsWithExternalReferences || new Map()); + elementsWithFuncIri.forEach( + (elementWithFuncIri) => { + funcIriAttributes.forEach(attr => { + const elementWithReference = elementWithFuncIri; + const value = elementWithReference.getAttribute(attr); + const match = value ? value.match(funcIriPattern) : null; + + if (match) { + let attributes = elements.get(elementWithReference); + + if (!attributes) { + attributes = []; + elements.set(elementWithReference, attributes); + } + + attributes.push({name: attr, value: match[1]}); + } + }); + } + ); + } + + private _prependPathToReferences(path: string) { + const elements = this._elementsWithExternalReferences; + if (elements) { + elements.forEach((attrs, element) => { + attrs.forEach(attr => { + element.setAttribute(attr.name, `url('${path}#${attr.value}')`); + }); + }); + } + } + +} diff --git a/ui-ngx/src/app/shared/components/material-icon-select.component.html b/ui-ngx/src/app/shared/components/material-icon-select.component.html index 8bae8c3435..8f7aa303f9 100644 --- a/ui-ngx/src/app/shared/components/material-icon-select.component.html +++ b/ui-ngx/src/app/shared/components/material-icon-select.component.html @@ -16,7 +16,7 @@ -->
- {{materialIconFormGroup.get('icon').value}} + {{materialIconFormGroup.get('icon').value}} {{ label }} @@ -24,18 +24,18 @@ type="button" matSuffix mat-icon-button aria-label="Clear" (click)="clear()"> - close + close
diff --git a/ui-ngx/src/app/shared/components/material-icon-select.component.scss b/ui-ngx/src/app/shared/components/material-icon-select.component.scss index b008fc6838..78e4d235cf 100644 --- a/ui-ngx/src/app/shared/components/material-icon-select.component.scss +++ b/ui-ngx/src/app/shared/components/material-icon-select.component.scss @@ -24,21 +24,3 @@ } } } - -:host ::ng-deep { - button.mat-mdc-button-base.icon-box { - width: 40px; - min-width: 40px; - height: 40px; - padding: 7px; - &:not(:disabled) { - color: rgba(0, 0, 0, 0.87); - } - > .mat-icon { - width: 24px; - height: 24px; - font-size: 24px; - margin: 0; - } - } -} diff --git a/ui-ngx/src/app/shared/components/material-icons.component.html b/ui-ngx/src/app/shared/components/material-icons.component.html index 39404a9998..d31a73ddb5 100644 --- a/ui-ngx/src/app/shared/components/material-icons.component.html +++ b/ui-ngx/src/app/shared/components/material-icons.component.html @@ -39,7 +39,7 @@ matTooltip="{{ icon.displayName }}" matTooltipPosition="above" type="button"> - {{icon.name}} + {{icon.name}}
diff --git a/ui-ngx/src/app/shared/components/notification/notification.component.html b/ui-ngx/src/app/shared/components/notification/notification.component.html index b9ce3a840f..ba6091eebc 100644 --- a/ui-ngx/src/app/shared/components/notification/notification.component.html +++ b/ui-ngx/src/app/shared/components/notification/notification.component.html @@ -18,14 +18,14 @@
- + {{ notification.additionalConfig.icon.icon }} - +
- + {{ notificationTypeIcons.get(notification.type) }} - +
diff --git a/ui-ngx/src/app/shared/components/public-api.ts b/ui-ngx/src/app/shared/components/public-api.ts index 04508266e9..35ab83900e 100644 --- a/ui-ngx/src/app/shared/components/public-api.ts +++ b/ui-ngx/src/app/shared/components/public-api.ts @@ -27,3 +27,4 @@ export * from './toggle-header.component'; export * from './toggle-select.component'; export * from './unit-input.component'; export * from './material-icons.component'; +export * from './icon.component'; diff --git a/ui-ngx/src/app/shared/models/icon.models.ts b/ui-ngx/src/app/shared/models/icon.models.ts index 48b643d235..8d7d4f65bd 100644 --- a/ui-ngx/src/app/shared/models/icon.models.ts +++ b/ui-ngx/src/app/shared/models/icon.models.ts @@ -19,6 +19,66 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { isNotEmptyStr } from '@core/utils'; +export const svgIcons: {[key: string]: string} = { + 'google-logo': '', + 'github-logo': '', + 'facebook-logo': '', + 'apple-logo': '', + 'queues-list': '' + + '' + + '' + + '' + + '' +}; + +const svgIconNamespaces: string[] = ['mdi']; +const svgIconNames = Object.keys(svgIcons); + +export const splitIconName = (iconName: string): [string, string] => { + if (!iconName) { + return ['', '']; + } + const parts = iconName.split(':'); + switch (parts.length) { + case 1: + return ['', parts[0]]; + case 2: + return parts as [string, string]; + default: + throw Error(`Invalid icon name: "${iconName}"`); + } +}; + +export const isSvgIcon = (icon: string): boolean => { + const [namespace, iconName] = splitIconName(icon); + return svgIconNamespaces.includes(namespace) || svgIconNames.includes(iconName); +}; + export interface MaterialIcon { name: string; displayName?: string; @@ -43,7 +103,8 @@ export const getMaterialIcons = (resourcesService: ResourcesService, chunkSize resourcesService.loadJsonResource>('/assets/metadata/material-icons.json', (icons) => { for (const icon of icons) { - const words = icon.name.replace(/_/g, ' ').split(' '); + const iconName = splitIconName(icon.name)[1]; + const words = iconName.replace(/[_\-]/g, ' ').split(' '); for (let i = 0; i < words.length; i++) { words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1); } diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index de4c5332f7..716a4cf8b4 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -57,7 +57,6 @@ export interface WidgetTypeTemplate { export interface WidgetTypeData { name: string; icon: string; - isMdiIcon?: boolean; configHelpLinkId: string; template: WidgetTypeTemplate; } @@ -94,7 +93,6 @@ export const widgetTypesData = new Map( name: 'widget.rpc', icon: 'mdi:developer-board', configHelpLinkId: 'widgetsConfigRpc', - isMdiIcon: true, template: { bundleAlias: 'gpio_widgets', alias: 'basic_gpio_control' @@ -182,6 +180,8 @@ export interface WidgetTypeParameters { warnOnPageDataOverflow?: boolean; ignoreDataUpdateOnIntervalTick?: boolean; processNoDataByWidget?: boolean; + previewWidth?: string; + previewHeight?: string; } export interface WidgetControllerDescriptor { diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 11c4b4effb..88d21fd0c5 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -197,6 +197,7 @@ import { ToggleSelectComponent } from '@shared/components/toggle-select.componen import { UnitInputComponent } from '@shared/components/unit-input.component'; import { MaterialIconsComponent } from '@shared/components/material-icons.component'; import { ColorPickerPanelComponent } from '@shared/components/color-picker/color-picker-panel.component'; +import { TbIconComponent } from '@shared/components/icon.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -373,7 +374,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) ToggleSelectComponent, UnitInputComponent, MaterialIconsComponent, - RuleChainSelectComponent + RuleChainSelectComponent, + TbIconComponent ], imports: [ CommonModule, @@ -606,7 +608,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) ToggleSelectComponent, UnitInputComponent, MaterialIconsComponent, - RuleChainSelectComponent + RuleChainSelectComponent, + TbIconComponent ] }) export class SharedModule { } diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/card/value_color_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/card/value_color_fn.md new file mode 100644 index 0000000000..639043bfbc --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/card/value_color_fn.md @@ -0,0 +1,40 @@ +#### Color function + +
+
+ +*function (value): string* + +A JavaScript function used to compute a color. + +**Parameters:** + +
    +
  • value: primitive (number/string/boolean) - A value of the current datapoint. +
  • +
+ +**Returns:** + +Should return string value presenting color. + +In case no data is returned, color value from **Color** settings field will be used. + +
+ +##### Examples + +* Calculate color depending on `temperature` telemetry value: + +```javascript +var temperature = value; +if (typeof temperature !== undefined) { + var percent = (temperature + 60)/120 * 100; + return tinycolor.mix('blue', 'red', percent).toHexString(); +} +return 'blue'; +{:copy-code} +``` + +
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/map/color_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/map/color_fn.md index 64e994fc3e..ede068d68f 100644 --- a/ui-ngx/src/assets/help/en_US/widget/lib/map/color_fn.md +++ b/ui-ngx/src/assets/help/en_US/widget/lib/map/color_fn.md @@ -31,7 +31,7 @@ if (type == 'colorpin') { var temperature = data['temperature']; if (typeof temperature !== undefined) { var percent = (temperature + 60)/120 * 100; - return tinycolor.mix('blue', 'red', amount = percent).toHexString(); + return tinycolor.mix('blue', 'red', percent).toHexString(); } return 'blue'; } diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/map/path_color_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/map/path_color_fn.md index 056e47d0eb..c4df0a99a9 100644 --- a/ui-ngx/src/assets/help/en_US/widget/lib/map/path_color_fn.md +++ b/ui-ngx/src/assets/help/en_US/widget/lib/map/path_color_fn.md @@ -31,7 +31,7 @@ if (type == 'colorpin') { var temperature = data['temperature']; if (typeof temperature !== undefined) { var percent = (temperature + 60)/120 * 100; - return tinycolor.mix('blue', 'red', amount = percent).toHexString(); + return tinycolor.mix('blue', 'red', percent).toHexString(); } return 'blue'; } diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_color_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_color_fn.md index 30f5e0f4d5..de092704c3 100644 --- a/ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_color_fn.md +++ b/ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_color_fn.md @@ -31,7 +31,7 @@ if (type == 'colorpin') { var temperature = data['temperature']; if (typeof temperature !== undefined) { var percent = (temperature + 60)/120 * 100; - return tinycolor.mix('blue', 'red', amount = percent).toHexString(); + return tinycolor.mix('blue', 'red', percent).toHexString(); } return 'blue'; } diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/map/polygon_color_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/map/polygon_color_fn.md index 736bd727f0..b4cc0c5223 100644 --- a/ui-ngx/src/assets/help/en_US/widget/lib/map/polygon_color_fn.md +++ b/ui-ngx/src/assets/help/en_US/widget/lib/map/polygon_color_fn.md @@ -31,7 +31,7 @@ if (type == 'thermostat') { var temperature = data['temperature']; if (typeof temperature !== undefined) { var percent = (temperature + 60)/120 * 100; - return tinycolor.mix('blue', 'red', amount = percent).toHexString(); + return tinycolor.mix('blue', 'red', percent).toHexString(); } return 'blue'; } 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 cd379fa4dc..1a93bf03db 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4407,6 +4407,17 @@ "ticks": "Ticks", "horizontal-axis": "Horizontal axis" }, + "color": { + "color-settings": "Color settings", + "color-type-constant": "Constant", + "color-type-range": "Range", + "color-type-function": "Function", + "color": "Color", + "value-range": "Value range", + "from": "From", + "to": "To", + "color-function": "Color function" + }, "dashboard-state": { "dashboard-state-settings": "Dashboard state settings", "dashboard-state": "Dashboard state id", @@ -5216,6 +5227,16 @@ "label-position-left": "Left", "label-position-top": "Top" }, + "value-card": { + "layout": "Layout", + "layout-square": "Square", + "layout-vertical": "Vertical", + "layout-centered": "Centered", + "layout-simplified": "Simplified", + "layout-horizontal": "Horizontal", + "layout-horizontal-reversed": "Horizontal reversed", + "label": "Label" + }, "table": { "common-table-settings": "Common Table Settings", "enable-search": "Enable search", @@ -5290,6 +5311,7 @@ "source-entity-attribute": "Source entity attribute" }, "widget-font": { + "font-settings": "Font settings", "font-family": "Font family", "size": "Size", "relative-font-size": "Relative font size (percents)", @@ -5303,7 +5325,8 @@ "font-weight-bolder": "Bolder", "font-weight-lighter": "Lighter", "color": "Color", - "shadow-color": "Shadow color" + "shadow-color": "Shadow color", + "preview": "Preview" }, "home": { "no-data-available": "No data available" diff --git a/ui-ngx/src/assets/metadata/material-icons.json b/ui-ngx/src/assets/metadata/material-icons.json index 7f12a1e605..2da54c48c2 100644 --- a/ui-ngx/src/assets/metadata/material-icons.json +++ b/ui-ngx/src/assets/metadata/material-icons.json @@ -1,6367 +1 @@ -[ { - "name" : "more_horiz", - "tags" : [ "3", "DISABLE_IOS", "app", "application", "components", "disable_ios", "dots", "etc", "horiz", "horizontal", "interface", "ios", "more", "screen", "site", "three", "ui", "ux", "web", "website" ] -}, { - "name" : "more_vert", - "tags" : [ "3", "DISABLE_IOS", "android", "app", "application", "components", "disable_ios", "dots", "etc", "interface", "more", "screen", "site", "three", "ui", "ux", "vert", "vertical", "web", "website" ] -}, { - "name" : "open_in_new", - "tags" : [ "app", "application", "arrow", "box", "components", "in", "interface", "new", "open", "right", "screen", "site", "ui", "up", "ux", "web", "website", "window" ] -}, { - "name" : "visibility", - "tags" : [ "eye", "on", "reveal", "see", "show", "view", "visibility" ] -}, { - "name" : "play_arrow", - "tags" : [ "arrow", "control", "controls", "media", "music", "play", "video" ] -}, { - "name" : "arrow_back", - "tags" : [ "DISABLE_IOS", "app", "application", "arrow", "back", "components", "direction", "disable_ios", "interface", "left", "navigation", "previous", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "arrow_downward", - "tags" : [ "app", "application", "arrow", "components", "direction", "down", "downward", "interface", "navigation", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "arrow_forward", - "tags" : [ "app", "application", "arrow", "arrows", "components", "direction", "forward", "interface", "navigation", "right", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "arrow_upward", - "tags" : [ "app", "application", "arrow", "components", "direction", "interface", "navigation", "screen", "site", "ui", "up", "upward", "ux", "web", "website" ] -}, { - "name" : "close", - "tags" : [ "cancel", "close", "exit", "stop", "x" ] -}, { - "name" : "refresh", - "tags" : [ "around", "arrow", "arrows", "direction", "inprogress", "load", "loading refresh", "navigation", "refresh", "renew", "right", "rotate", "turn" ] -}, { - "name" : "menu", - "tags" : [ "app", "application", "components", "hamburger", "interface", "line", "lines", "menu", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "show_chart", - "tags" : [ "analytics", "bar", "bars", "chart", "data", "diagram", "graph", "infographic", "line", "measure", "metrics", "presentation", "show chart", "statistics", "tracking" ] -}, { - "name" : "multiline_chart", - "tags" : [ "analytics", "bar", "bars", "chart", "data", "diagram", "graph", "infographic", "line", "measure", "metrics", "multiple", "statistics", "tracking" ] -}, { - "name" : "pie_chart", - "tags" : [ "analytics", "bar", "bars", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "pie", "statistics", "tracking" ] -}, { - "name" : "insert_chart", - "tags" : [ "analytics", "bar", "bars", "chart", "data", "diagram", "graph", "infographic", "insert", "measure", "metrics", "statistics", "tracking" ] -}, { - "name" : "people", - "tags" : [ "accounts", "committee", "face", "family", "friends", "humans", "network", "people", "persons", "profiles", "social", "team", "users" ] -}, { - "name" : "person", - "tags" : [ "account", "face", "human", "people", "person", "profile", "user" ] -}, { - "name" : "domain", - "tags" : [ "apartment", "architecture", "building", "business", "domain", "estate", "home", "place", "real", "residence", "residential", "shelter", "web", "www" ] -}, { - "name" : "devices_other", - "tags" : [ "Android", "OS", "ar", "cell", "chrome", "desktop", "device", "gadget", "hardware", "iOS", "ipad", "mac", "mobile", "monitor", "other", "phone", "tablet", "vr", "watch", "wearables", "window" ] -}, { - "name" : "widgets", - "tags" : [ "app", "box", "menu", "setting", "squares", "ui", "widgets" ] -}, { - "name" : "dashboard", - "tags" : [ "cards", "dashboard", "format", "layout", "rectangle", "shapes", "square", "web", "website" ] -}, { - "name" : "map", - "tags" : [ "destination", "direction", "location", "map", "maps", "pin", "place", "route", "stop", "travel" ] -}, { - "name" : "pin_drop", - "tags" : [ "destination", "direction", "drop", "location", "maps", "navigation", "pin", "place", "stop" ] -}, { - "name" : "gps_fixed", - "tags" : [ "destination", "direction", "fixed", "gps", "location", "maps", "pin", "place", "pointer", "stop", "tracking" ] -}, { - "name" : "extension", - "tags" : [ "app", "extended", "extension", "game", "jigsaw", "plugin add", "puzzle", "shape" ] -}, { - "name" : "search", - "tags" : [ "filter", "find", "glass", "look", "magnify", "magnifying", "search", "see" ] -}, { - "name" : "settings", - "tags" : [ "application", "change", "details", "gear", "info", "information", "options", "personal", "service", "settings" ] -}, { - "name" : "notifications", - "tags" : [ "active", "alarm", "alert", "bell", "chime", "notifications", "notify", "reminder", "ring", "sound" ] -}, { - "name" : "notifications_active", - "tags" : [ "active", "alarm", "alert", "bell", "chime", "notifications", "notify", "reminder", "ring", "ringing", "sound" ] -}, { - "name" : "info", - "tags" : [ "alert", "announcement", "assistance", "details", "help", "i", "info", "information", "service", "support" ] -}, { - "name" : "error_outline", - "tags" : [ "!", "alert", "attention", "caution", "circle", "danger", "error", "exclamation", "important", "mark", "notification", "outline", "symbol", "warning" ] -}, { - "name" : "warning", - "tags" : [ "!", "alert", "attention", "caution", "danger", "error", "exclamation", "important", "mark", "notification", "symbol", "triangle", "warning" ] -}, { - "name" : "list", - "tags" : [ "file", "format", "index", "list", "menu", "options" ] -}, { - "name" : "download", - "tags" : [ "arrow", "down", "download", "downloads", "drive", "install", "upload" ] -}, { - "name" : "import_export", - "tags" : [ "arrow", "arrows", "direction", "down", "explort", "import", "up" ] -}, { - "name" : "share", - "tags" : [ "DISABLE_IOS", "android", "connect", "contect", "disable_ios", "link", "media", "multimedia", "multiple", "network", "options", "share", "shared", "sharing", "social" ] -}, { - "name" : "add", - "tags" : [ "+", "add", "new symbol", "plus", "symbol" ] -}, { - "name" : "edit", - "tags" : [ "compose", "create", "edit", "editing", "input", "new", "pen", "pencil", "write", "writing" ] -}, { - "name" : "check", - "tags" : [ "DISABLE_IOS", "check", "confirm", "correct", "disable_ios", "done", "enter", "mark", "ok", "okay", "select", "tick", "yes" ] -}, { - "name" : "delete", - "tags" : [ "bin", "can", "delete", "garbage", "remove", "trash" ] -}, { - "name" : "thermostat", - "tags" : [ "climate", "forecast", "temperature", "thermostat", "weather" ] -}, { - "name" : "air", - "tags" : [ "air", "blowing", "breeze", "flow", "wave", "weather", "wind" ] -}, { - "name" : "lightbulb", - "tags" : [ "alert", "announcement", "idea", "info", "information", "light", "lightbulb" ] -}, { - "name" : "home", - "tags" : [ "address", "app", "application--house", "architecture", "building", "components", "design", "estate", "home", "interface", "layout", "place", "real", "residence", "residential", "screen", "shelter", "site", "structure", "ui", "unit", "ux", "web", "website", "window" ] -}, { - "name" : "account_circle", - "tags" : [ "account", "avatar", "circle", "face", "human", "people", "person", "profile", "thumbnail", "user" ] -}, { - "name" : "done", - "tags" : [ "DISABLE_IOS", "approve", "check", "complete", "disable_ios", "done", "mark", "ok", "select", "tick", "validate", "verified", "yes" ] -}, { - "name" : "check_circle", - "tags" : [ "approve", "check", "circle", "complete", "done", "mark", "ok", "select", "tick", "validate", "verified", "yes" ] -}, { - "name" : "expand_more", - "tags" : [ "arrow", "arrows", "chevron", "collapse", "direction", "down", "expand", "expandable", "list", "more" ] -}, { - "name" : "shopping_cart", - "tags" : [ "add", "bill", "buy", "card", "cart", "cash", "checkout", "coin", "commerce", "credit", "currency", "dollars", "money", "online", "pay", "payment", "shopping" ] -}, { - "name" : "email", - "tags" : [ "email", "envelop", "letter", "mail", "message", "send" ] -}, { - "name" : "favorite", - "tags" : [ "appreciate", "favorite", "heart", "like", "love", "remember", "save", "shape" ] -}, { - "name" : "description", - "tags" : [ "article", "data", "description", "doc", "document", "drive", "file", "folder", "folders", "notes", "page", "paper", "sheet", "slide", "text", "writing" ] -}, { - "name" : "logout", - "tags" : [ "app", "application", "arrow", "components", "design", "exit", "interface", "leave", "log", "login", "logout", "right", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "favorite_border", - "tags" : [ "border", "favorite", "heart", "like", "love", "outline", "remember", "save", "shape" ] -}, { - "name" : "chevron_right", - "tags" : [ "arrow", "arrows", "chevron", "direction", "right" ] -}, { - "name" : "lock", - "tags" : [ "lock", "locked", "password", "privacy", "private", "protection", "safety", "secure", "security" ] -}, { - "name" : "location_on", - "tags" : [ "destination", "direction", "location", "maps", "on", "pin", "place", "room", "stop" ] -}, { - "name" : "schedule", - "tags" : [ "clock", "date", "schedule", "time" ] -}, { - "name" : "local_shipping", - "tags" : [ "automobile", "car", "cars", "delivery", "letter", "local", "mail", "maps", "office", "package", "parcel", "post", "postal", "send", "shipping", "shopping", "stamp", "transportation", "truck", "vehicle" ] -}, { - "name" : "language", - "tags" : [ "globe", "internet", "language", "planet", "website", "world", "www" ] -}, { - "name" : "call", - "tags" : [ "call", "cell", "contact", "device", "hardware", "mobile", "phone", "telephone" ] -}, { - "name" : "file_download", - "tags" : [ "arrow", "arrows", "down", "download", "downloads", "drive", "export", "file", "install", "upload" ] -}, { - "name" : "arrow_forward_ios", - "tags" : [ "app", "application", "arrow", "chevron", "components", "direction", "forward", "interface", "ios", "navigation", "next", "right", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "arrow_back_ios", - "tags" : [ "DISABLE_IOS", "app", "application", "arrow", "back", "chevron", "components", "direction", "disable_ios", "interface", "ios", "left", "navigation", "previous", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "groups", - "tags" : [ "body", "club", "collaboration", "crowd", "gathering", "groups", "human", "meeting", "people", "person", "social", "teams" ] -}, { - "name" : "cancel", - "tags" : [ "cancel", "circle", "close", "exit", "stop", "x" ] -}, { - "name" : "help_outline", - "tags" : [ "?", "assistance", "circle", "help", "info", "information", "outline", "punctuation", "question mark", "recent", "restore", "shape", "support", "symbol" ] -}, { - "name" : "arrow_drop_down", - "tags" : [ "app", "application", "arrow", "components", "direction", "down", "drop", "interface", "navigation", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "face", - "tags" : [ "account", "emoji", "eyes", "face", "human", "lock", "log", "login", "logout", "people", "person", "profile", "recognition", "security", "social", "thumbnail", "unlock", "user" ] -}, { - "name" : "manage_accounts", - "tags" : [ "accounts", "change", "details service-human", "face", "gear", "manage", "options", "people", "person", "profile", "settings", "user" ] -}, { - "name" : "place", - "tags" : [ "destination", "direction", "location", "maps", "navigation", "pin", "place", "point", "stop" ] -}, { - "name" : "verified", - "tags" : [ "approve", "badge", "burst", "check", "complete", "done", "mark", "ok", "select", "star", "tick", "validate", "verified", "yes" ] -}, { - "name" : "add_circle_outline", - "tags" : [ "+", "add", "circle", "create", "new", "outline", "plus" ] -}, { - "name" : "filter_alt", - "tags" : [ "alt", "edit", "filter", "funnel", "options", "refine", "sift" ] -}, { - "name" : "thumb_up", - "tags" : [ "favorite", "fingers", "gesture", "hand", "hands", "like", "rank", "ranking", "rate", "rating", "thumb", "up" ] -}, { - "name" : "event", - "tags" : [ "calendar", "date", "day", "event", "mark", "month", "range", "remember", "reminder", "today", "week" ] -}, { - "name" : "star", - "tags" : [ "best", "bookmark", "favorite", "highlight", "ranking", "rate", "rating", "save", "star", "toggle" ] -}, { - "name" : "fingerprint", - "tags" : [ "finger", "fingerprint", "id", "identification", "identity", "print", "reader", "thumbprint", "verification" ] -}, { - "name" : "content_copy", - "tags" : [ "content", "copy", "cut", "doc", "document", "duplicate", "file", "multiple", "past" ] -}, { - "name" : "login", - "tags" : [ "access", "app", "application", "arrow", "components", "design", "enter", "in", "interface", "left", "log", "login", "screen", "sign", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "add_circle", - "tags" : [ "+", "add", "circle", "create", "new", "plus" ] -}, { - "name" : "visibility_off", - "tags" : [ "disabled", "enabled", "eye", "off", "on", "reveal", "see", "show", "slash", "view", "visibility" ] -}, { - "name" : "check_circle_outline", - "tags" : [ "approve", "check", "circle", "complete", "done", "finished", "mark", "ok", "outline", "select", "tick", "validate", "verified", "yes" ] -}, { - "name" : "chevron_left", - "tags" : [ "DISABLE_IOS", "arrow", "arrows", "chevron", "direction", "disable_ios", "left" ] -}, { - "name" : "calendar_today", - "tags" : [ "calendar", "date", "day", "event", "month", "schedule", "today" ] -}, { - "name" : "send", - "tags" : [ "email", "mail", "message", "paper", "plane", "reply", "right", "send", "share" ] -}, { - "name" : "check_box", - "tags" : [ "approved", "box", "button", "check", "component", "control", "form", "mark", "ok", "select", "selected", "selection", "tick", "toggle", "ui", "yes" ] -}, { - "name" : "highlight_off", - "tags" : [ "cancel", "close", "exit", "highlight", "no", "off", "quit", "remove", "stop", "x" ] -}, { - "name" : "navigate_next", - "tags" : [ "arrow", "arrows", "direction", "navigate", "next", "right" ] -}, { - "name" : "help", - "tags" : [ "?", "assistance", "circle", "help", "info", "information", "punctuation", "question mark", "recent", "restore", "shape", "support", "symbol" ] -}, { - "name" : "phone", - "tags" : [ "call", "cell", "contact", "device", "hardware", "mobile", "phone", "telephone" ] -}, { - "name" : "paid", - "tags" : [ "circle", "currency", "money", "paid", "payment", "transaction" ] -}, { - "name" : "task_alt", - "tags" : [ "approve", "check", "circle", "complete", "done", "mark", "ok", "select", "task", "tick", "validate", "verified", "yes" ] -}, { - "name" : "question_answer", - "tags" : [ "answer", "bubble", "chat", "comment", "communicate", "conversation", "feedback", "message", "question", "speech", "talk" ] -}, { - "name" : "expand_less", - "tags" : [ "arrow", "arrows", "chevron", "collapse", "direction", "expand", "expandable", "less", "list", "up" ] -}, { - "name" : "clear", - "tags" : [ "back", "cancel", "clear", "correct", "delete", "erase", "exit", "x" ] -}, { - "name" : "date_range", - "tags" : [ "calendar", "date", "day", "event", "month", "range", "remember", "reminder", "schedule", "time", "today", "week" ] -}, { - "name" : "article", - "tags" : [ "article", "doc", "document", "file", "page", "paper", "text", "writing" ] -}, { - "name" : "error", - "tags" : [ "!", "alert", "attention", "caution", "circle", "danger", "error", "exclamation", "important", "mark", "notification", "symbol", "warning" ] -}, { - "name" : "photo_camera", - "tags" : [ "camera", "image", "photo", "photography", "picture" ] -}, { - "name" : "check_box_outline_blank", - "tags" : [ "blank", "box", "button", "check", "component", "control", "deselected", "empty", "form", "outline", "select", "selection", "square", "tick", "toggle", "ui" ] -}, { - "name" : "image", - "tags" : [ "disabled", "enabled", "hide", "image", "landscape", "mountain", "mountains", "off", "on", "photo", "photography", "picture", "slash" ] -}, { - "name" : "shopping_bag", - "tags" : [ "bag", "bill", "business", "buy", "card", "cart", "cash", "coin", "commerce", "credit", "currency", "dollars", "money", "online", "pay", "payment", "shop", "shopping", "store", "storefront" ] -}, { - "name" : "person_outline", - "tags" : [ "account", "face", "human", "outline", "people", "person", "profile", "user" ] -}, { - "name" : "school", - "tags" : [ "academy", "achievement", "cap", "class", "college", "education", "graduation", "hat", "knowledge", "learning", "school", "university" ] -}, { - "name" : "file_upload", - "tags" : [ "arrow", "arrows", "download", "drive", "export", "file", "up", "upload" ] -}, { - "name" : "perm_identity", - "tags" : [ "account", "avatar", "face", "human", "identity", "people", "perm", "person", "profile", "thumbnail", "user" ] -}, { - "name" : "credit_card", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "money", "online", "pay", "payment", "price", "shopping", "symbol" ] -}, { - "name" : "history", - "tags" : [ "arrow", "back", "backwards", "clock", "date", "history", "refresh", "renew", "reverse", "rotate", "schedule", "time", "turn" ] -}, { - "name" : "trending_up", - "tags" : [ "analytics", "arrow", "data", "diagram", "graph", "infographic", "measure", "metrics", "movement", "rate", "rating", "statistics", "tracking", "trending", "up" ] -}, { - "name" : "support_agent", - "tags" : [ "agent", "care", "customer", "face", "headphone", "person", "representative", "service", "support" ] -}, { - "name" : "account_balance", - "tags" : [ "account", "balance", "bank", "bill", "card", "cash", "coin", "commerce", "credit", "currency", "dollars", "finance", "money", "online", "pay", "payment" ] -}, { - "name" : "delete_outline", - "tags" : [ "bin", "can", "delete", "garbage", "outline", "remove", "trash" ] -}, { - "name" : "attach_money", - "tags" : [ "attach", "attachment", "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "money", "online", "pay", "payment", "symbol" ] -}, { - "name" : "person_add", - "tags" : [ "+", "account", "add", "avatar", "face", "human", "new", "people", "person", "plus", "profile", "symbol", "user" ] -}, { - "name" : "public", - "tags" : [ "earth", "global", "globe", "map", "network", "planet", "public", "social", "space", "web", "world" ] -}, { - "name" : "save", - "tags" : [ "data", "disk", "document", "drive", "file", "floppy", "multimedia", "save", "storage" ] -}, { - "name" : "mail", - "tags" : [ "email", "envelop", "letter", "mail", "message", "send" ] -}, { - "name" : "report_problem", - "tags" : [ "!", "alert", "attention", "caution", "danger", "error", "exclamation", "feedback", "important", "mark", "notification", "problem", "report", "symbol", "triangle", "warning" ] -}, { - "name" : "fact_check", - "tags" : [ "approve", "check", "complete", "done", "fact", "list", "mark", "ok", "select", "tick", "validate", "verified", "yes" ] -}, { - "name" : "radio_button_unchecked", - "tags" : [ "bullet", "button", "circle", "deselected", "form", "off", "on", "point", "radio", "record", "select", "toggle", "unchecked" ] -}, { - "name" : "verified_user", - "tags" : [ "approve", "certified", "check", "complete", "done", "mark", "ok", "privacy", "private", "protect", "protection", "security", "select", "shield", "tick", "user", "validate", "verified", "yes" ] -}, { - "name" : "assignment", - "tags" : [ "assignment", "clipboard", "doc", "document", "text", "writing" ] -}, { - "name" : "link", - "tags" : [ "chain", "clip", "connection", "link", "linked", "links", "multimedia", "url" ] -}, { - "name" : "play_circle_filled", - "tags" : [ "arrow", "circle", "control", "controls", "media", "music", "play", "video" ] -}, { - "name" : "emoji_events", - "tags" : [ "achievement", "award", "chalice", "champion", "cup", "emoji", "events", "first", "prize", "reward", "sport", "trophy", "winner" ] -}, { - "name" : "remove", - "tags" : [ "can", "delete", "minus", "negative", "remove", "substract", "trash" ] -}, { - "name" : "star_rate", - "tags" : [ "achievement", "bookmark", "favorite", "highlight", "important", "marked", "ranking", "rate", "rating rank", "reward", "save", "saved", "shape", "special", "star" ] -}, { - "name" : "apps", - "tags" : [ "all", "applications", "apps", "circles", "collection", "components", "dots", "grid", "interface", "squares", "ui", "ux" ] -}, { - "name" : "business", - "tags" : [ "apartment", "architecture", "building", "business", "company", "estate", "home", "place", "real", "residence", "residential", "shelter" ] -}, { - "name" : "filter_list", - "tags" : [ "filter", "lines", "list", "organize", "sort" ] -}, { - "name" : "arrow_right_alt", - "tags" : [ "alt", "arrow", "arrows", "direction", "east", "navigation", "pointing", "right" ] -}, { - "name" : "chat", - "tags" : [ "bubble", "chat", "comment", "communicate", "feedback", "message", "speech" ] -}, { - "name" : "account_balance_wallet", - "tags" : [ "account", "balance", "bank", "bill", "card", "cash", "coin", "commerce", "credit", "currency", "dollars", "finance", "money", "online", "pay", "payment", "wallet" ] -}, { - "name" : "payments", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "layer", "money", "multiple", "online", "pay", "payment", "payments", "price", "shopping", "symbol" ] -}, { - "name" : "menu_book", - "tags" : [ "book", "dining", "food", "meal", "menu", "restaurant" ] -}, { - "name" : "folder", - "tags" : [ "data", "doc", "document", "drive", "file", "folder", "folders", "sheet", "slide", "storage" ] -}, { - "name" : "keyboard_arrow_down", - "tags" : [ "arrow", "arrows", "down", "keyboard" ] -}, { - "name" : "autorenew", - "tags" : [ "around", "arrow", "arrows", "autorenew", "cache", "cached", "direction", "inprogress", "load", "loading refresh", "navigation", "renew", "rotate", "turn" ] -}, { - "name" : "build", - "tags" : [ "adjust", "build", "fix", "home", "nest", "repair", "tool", "tools", "wrench" ] -}, { - "name" : "videocam", - "tags" : [ "cam", "camera", "conference", "film", "filming", "hardware", "image", "motion", "picture", "video", "videography" ] -}, { - "name" : "view_list", - "tags" : [ "design", "format", "grid", "layout", "lines", "list", "stacked", "view", "website" ] -}, { - "name" : "print", - "tags" : [ "draft", "fax", "ink", "machine", "office", "paper", "print", "printer", "send" ] -}, { - "name" : "work", - "tags" : [ "bag", "baggage", "briefcase", "business", "case", "job", "suitcase", "work" ] -}, { - "name" : "store", - "tags" : [ "bill", "building", "business", "card", "cash", "coin", "commerce", "company", "credit", "currency", "dollars", "market", "money", "online", "pay", "payment", "shop", "shopping", "store", "storefront" ] -}, { - "name" : "analytics", - "tags" : [ "analytics", "assessment", "bar", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "statistics", "tracking" ] -}, { - "name" : "radio_button_checked", - "tags" : [ "app", "application", "bullet", "button", "checked", "circle", "components", "design", "form", "interface", "off", "on", "point", "radio", "record", "screen", "select", "selected", "site", "toggle", "ui", "ux", "web", "website" ] -}, { - "name" : "phone_iphone", - "tags" : [ "Android", "OS", "cell", "device", "hardware", "iOS", "iphone", "mobile", "phone", "tablet" ] -}, { - "name" : "play_circle", - "tags" : [ "arrow", "circle", "control", "controls", "media", "music", "play", "video" ] -}, { - "name" : "tune", - "tags" : [ "adjust", "audio", "controls", "custom", "customize", "edit", "editing", "filter", "filters", "instant", "mix", "music", "options", "setting", "settings", "slider", "sliders", "switches", "tune" ] -}, { - "name" : "delete_forever", - "tags" : [ "bin", "can", "cancel", "delete", "exit", "forever", "garbage", "remove", "trash", "x" ] -}, { - "name" : "today", - "tags" : [ "calendar", "date", "day", "event", "mark", "month", "remember", "reminder", "schedule", "time", "today" ] -}, { - "name" : "grid_view", - "tags" : [ "app", "application square", "blocks", "components", "dashboard", "design", "grid", "interface", "layout", "screen", "site", "tiles", "ui", "ux", "view", "web", "website", "window" ] -}, { - "name" : "east", - "tags" : [ "arrow", "directional", "east", "maps", "navigation", "right" ] -}, { - "name" : "inventory_2", - "tags" : [ "archive", "box", "file", "inventory", "organize", "packages", "product", "stock", "storage", "supply" ] -}, { - "name" : "mail_outline", - "tags" : [ "email", "envelop", "letter", "mail", "message", "outline", "send" ] -}, { - "name" : "admin_panel_settings", - "tags" : [ "account", "admin", "avatar", "certified", "face", "human", "panel", "people", "person", "privacy", "private", "profile", "protect", "protection", "security", "settings", "shield", "user", "verified" ] -}, { - "name" : "mic", - "tags" : [ "hear", "hearing", "mic", "microphone", "noise", "record", "sound", "voice" ] -}, { - "name" : "calendar_month", - "tags" : [ "calendar", "date", "day", "event", "month", "schedule", "today" ] -}, { - "name" : "group", - "tags" : [ "accounts", "committee", "face", "family", "friends", "group", "humans", "network", "people", "persons", "profiles", "social", "team", "users" ] -}, { - "name" : "picture_as_pdf", - "tags" : [ "alphabet", "as", "character", "document", "file", "font", "image", "letter", "multiple", "pdf", "photo", "photography", "picture", "symbol", "text", "type" ] -}, { - "name" : "lock_open", - "tags" : [ "lock", "open", "password", "privacy", "private", "protection", "safety", "secure", "security", "unlocked" ] -}, { - "name" : "volume_up", - "tags" : [ "audio", "control", "music", "sound", "speaker", "tv", "up", "volume" ] -}, { - "name" : "watch_later", - "tags" : [ "clock", "date", "later", "schedule", "time", "watch" ] -}, { - "name" : "grade", - "tags" : [ "'favorite_news' .", "'star_outline'", "Duplicate of 'star_boarder'", "star_border_purple500'" ] -}, { - "name" : "receipt_long", - "tags" : [ "bill", "check", "document", "list", "long", "paper", "paperwork", "receipt", "record", "store", "transaction" ] -}, { - "name" : "local_offer", - "tags" : [ "deal", "discount", "offer", "price", "shop", "shopping", "store", "tag" ] -}, { - "name" : "room", - "tags" : [ "destination", "direction", "location", "maps", "pin", "place", "room", "stop" ] -}, { - "name" : "update", - "tags" : [ "arrow", "back", "backwards", "clock", "forward", "history", "load", "refresh", "reverse", "schedule", "time", "update" ] -}, { - "name" : "badge", - "tags" : [ "account", "avatar", "badge", "card", "certified", "employee", "face", "human", "identification", "name", "people", "person", "profile", "security", "user", "work" ] -}, { - "name" : "savings", - "tags" : [ "bank", "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "money", "online", "pay", "payment", "pig", "piggy", "savings", "symbol" ] -}, { - "name" : "code", - "tags" : [ "brackets", "code", "css", "develop", "developer", "engineer", "engineering", "html", "platform" ] -}, { - "name" : "light_mode", - "tags" : [ "bright", "brightness", "day", "device", "light", "lighting", "mode", "morning", "sky", "sun", "sunny" ] -}, { - "name" : "receipt", - "tags" : [ ] -}, { - "name" : "circle", - "tags" : [ "circle", "full", "geometry", "moon" ] -}, { - "name" : "inventory", - "tags" : [ "archive", "box", "clipboard", "doc", "document", "file", "inventory", "organize", "packages", "product", "stock", "supply" ] -}, { - "name" : "add_shopping_cart", - "tags" : [ "add", "card", "cart", "cash", "checkout", "coin", "commerce", "credit", "currency", "dollars", "money", "online", "pay", "payment", "plus", "shopping" ] -}, { - "name" : "contact_support", - "tags" : [ "?", "bubble", "chat", "comment", "communicate", "contact", "help", "info", "information", "mark", "message", "punctuation", "question", "question mark", "speech", "support", "symbol" ] -}, { - "name" : "category", - "tags" : [ "categories", "category", "circle", "collection", "items", "product", "sort", "square", "triangle" ] -}, { - "name" : "edit_note", - "tags" : [ "compose", "create", "draft", "edit", "editing", "input", "lines", "note", "pen", "pencil", "text", "write", "writing" ] -}, { - "name" : "insights", - "tags" : [ "ai", "analytics", "artificial", "automatic", "automation", "bar", "bars", "chart", "custom", "data", "diagram", "genai", "graph", "infographic", "insights", "intelligence", "magic", "measure", "metrics", "smart", "spark", "sparkle", "star", "stars", "statistics", "tracking" ] -}, { - "name" : "power_settings_new", - "tags" : [ "info", "information", "off", "on", "power", "save", "settings", "shutdown" ] -}, { - "name" : "campaign", - "tags" : [ "alert", "announcement", "campaign", "loud", "megaphone", "microphone", "notification", "speaker" ] -}, { - "name" : "format_list_bulleted", - "tags" : [ "align", "alignment", "bulleted", "doc", "edit", "editing", "editor", "format", "list", "notes", "sheet", "spreadsheet", "text", "type", "writing" ] -}, { - "name" : "star_border", - "tags" : [ "best", "bookmark", "border", "favorite", "highlight", "outline", "ranking", "rate", "rating", "save", "star", "toggle" ] -}, { - "name" : "pause", - "tags" : [ "control", "controls", "media", "music", "pause", "video" ] -}, { - "name" : "remove_circle_outline", - "tags" : [ "block", "can", "circle", "delete", "minus", "negative", "outline", "remove", "substract", "trash" ] -}, { - "name" : "warning_amber", - "tags" : [ "!", "alert", "amber", "attention", "caution", "danger", "error", "exclamation", "important", "mark", "notification", "symbol", "triangle", "warning" ] -}, { - "name" : "wifi", - "tags" : [ "connection", "data", "internet", "network", "scan", "service", "signal", "wifi", "wireless" ] -}, { - "name" : "arrow_back_ios_new", - "tags" : [ "DISABLE_IOS", "app", "application", "arrow", "back", "chevron", "components", "direction", "disable_ios", "interface", "ios", "left", "navigation", "new", "previous", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "restart_alt", - "tags" : [ "alt", "around", "arrow", "inprogress", "load", "loading refresh", "reboot", "renew", "repeat", "reset", "restart" ] -}, { - "name" : "done_all", - "tags" : [ "all", "approve", "check", "complete", "done", "layers", "mark", "multiple", "ok", "select", "stack", "tick", "validate", "verified", "yes" ] -}, { - "name" : "pets", - "tags" : [ "animal", "cat", "dog", "hand", "paw", "pet" ] -}, { - "name" : "storefront", - "tags" : [ "business", "buy", "cafe", "commerce", "front", "market", "places", "restaurant", "retail", "sell", "shop", "shopping", "store", "storefront" ] -}, { - "name" : "sort", - "tags" : [ "filter", "find", "lines", "list", "organize", "sort" ] -}, { - "name" : "mode_edit", - "tags" : [ "compose", "create", "draft", "draw", "edit", "mode", "pen", "pencil", "write" ] -}, { - "name" : "list_alt", - "tags" : [ "alt", "box", "contained", "format", "lines", "list", "order", "reorder", "stacked", "title" ] -}, { - "name" : "toggle_on", - "tags" : [ "active", "app", "application", "components", "configuration", "control", "design", "disable", "inable", "inactive", "interface", "off", "on", "selection", "settings", "site", "slider", "switch", "toggle", "ui", "ux", "web", "website" ] -}, { - "name" : "dark_mode", - "tags" : [ "app", "application", "dark", "device", "interface", "mode", "moon", "night", "silent", "theme", "ui", "ux", "website" ] -}, { - "name" : "engineering", - "tags" : [ "body", "cogs", "cogwheel", "construction", "engineering", "fixing", "gears", "hat", "helmet", "human", "maintenance", "people", "person", "setting", "worker" ] -}, { - "name" : "explore", - "tags" : [ "compass", "destination", "direction", "east", "explore", "location", "maps", "needle", "north", "south", "travel", "west" ] -}, { - "name" : "bolt", - "tags" : [ "bolt", "electric", "energy", "fast", "flash", "lightning", "power", "thunderbolt" ] -}, { - "name" : "construction", - "tags" : [ "build", "carpenter", "construction", "equipment", "fix", "hammer", "improvement", "industrial", "industry", "repair", "tools", "wrench" ] -}, { - "name" : "qr_code_scanner", - "tags" : [ "barcode", "camera", "code", "media", "product", "qr", "quick", "response", "scanner", "smartphone", "url", "urls" ] -}, { - "name" : "bookmark", - "tags" : [ "archive", "bookmark", "favorite", "label", "library", "read", "reading", "remember", "ribbon", "save", "tag" ] -}, { - "name" : "vpn_key", - "tags" : [ "code", "key", "lock", "network", "passcode", "password", "unlock", "vpn" ] -}, { - "name" : "monetization_on", - "tags" : [ "bill", "card", "cash", "circle", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "monetization", "money", "on", "online", "pay", "payment", "shopping", "symbol" ] -}, { - "name" : "attach_file", - "tags" : [ "add", "attach", "attachment", "clip", "file", "link", "mail", "media" ] -}, { - "name" : "timer", - "tags" : [ "alarm", "alert", "bell", "clock", "disabled", "duration", "enabled", "notification", "off", "on", "slash", "stop", "time", "timer", "watch" ] -}, { - "name" : "account_box", - "tags" : [ "account", "avatar", "box", "face", "human", "people", "person", "profile", "square", "thumbnail", "user" ] -}, { - "name" : "note_add", - "tags" : [ "+", "-doc", "add", "data", "document", "drive", "file", "folder", "folders", "new", "note", "page", "paper", "plus", "sheet", "slide", "symbol", "writing" ] -}, { - "name" : "reorder", - "tags" : [ "format", "lines", "list", "order", "reorder", "stacked" ] -}, { - "name" : "bookmark_border", - "tags" : [ "archive", "bookmark", "border", "favorite", "label", "library", "read", "reading", "remember", "ribbon", "save", "tag" ] -}, { - "name" : "arrow_right", - "tags" : [ "app", "application", "arrow", "components", "direction", "interface", "navigation", "right", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "pending_actions", - "tags" : [ "actions", "clipboard", "clock", "date", "doc", "document", "pending", "remember", "schedule", "time" ] -}, { - "name" : "smartphone", - "tags" : [ "Android", "OS", "call", "cell", "chat", "device", "hardware", "iOS", "mobile", "phone", "smartphone", "tablet", "text" ] -}, { - "name" : "upload_file", - "tags" : [ "arrow", "data", "doc", "document", "download", "drive", "file", "folder", "folders", "page", "paper", "sheet", "slide", "up", "upload", "writing" ] -}, { - "name" : "account_tree", - "tags" : [ "account", "analytics", "chart", "connect", "data", "diagram", "flow", "graph", "infographic", "measure", "metrics", "process", "square", "statistics", "structure", "tracking", "tree" ] -}, { - "name" : "shopping_basket", - "tags" : [ "add", "basket", "bill", "buy", "card", "cart", "cash", "checkout", "coin", "commerce", "credit", "currency", "dollars", "money", "online", "pay", "payment", "shopping" ] -}, { - "name" : "flag", - "tags" : [ "country", "flag", "goal", "mark", "nation", "report", "start" ] -}, { - "name" : "apartment", - "tags" : [ "accommodation", "apartment", "architecture", "building", "city", "company", "estate", "flat", "home", "house", "office", "places", "real", "residence", "residential", "shelter", "units", "workplace" ] -}, { - "name" : "restaurant", - "tags" : [ "breakfast", "dining", "dinner", "eat", "food", "fork", "knife", "local", "lunch", "meal", "places", "restaurant", "spoon", "utensils" ] -}, { - "name" : "people_alt", - "tags" : [ "accounts", "committee", "face", "family", "friends", "humans", "network", "people", "persons", "profiles", "social", "team", "users" ] -}, { - "name" : "reply", - "tags" : [ "arrow", "backward", "left", "mail", "message", "reply", "send", "share" ] -}, { - "name" : "play_circle_outline", - "tags" : [ "arrow", "circle", "control", "controls", "media", "music", "outline", "play", "video" ] -}, { - "name" : "payment", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "money", "online", "pay", "payment", "price", "shopping", "symbol" ] -}, { - "name" : "sync", - "tags" : [ "360", "around", "arrow", "arrows", "direction", "inprogress", "load", "loading refresh", "renew", "rotate", "sync", "turn" ] -}, { - "name" : "task", - "tags" : [ "approve", "check", "complete", "data", "doc", "document", "done", "drive", "file", "folder", "folders", "mark", "ok", "page", "paper", "select", "sheet", "slide", "task", "tick", "validate", "verified", "writing", "yes" ] -}, { - "name" : "launch", - "tags" : [ "app", "application", "arrow", "box", "components", "interface", "launch", "new", "open", "screen", "site", "ui", "ux", "web", "website", "window" ] -}, { - "name" : "menu_open", - "tags" : [ "app", "application", "arrow", "components", "hamburger", "interface", "left", "line", "lines", "menu", "open", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "add_box", - "tags" : [ "add", "box", "new square", "plus", "symbol" ] -}, { - "name" : "drag_indicator", - "tags" : [ "app", "application", "circles", "components", "design", "dots", "drag", "drop", "indicator", "interface", "layout", "mobile", "monitor", "move", "phone", "screen", "shape", "shift", "site", "tablet", "ui", "ux", "web", "website", "window" ] -}, { - "name" : "supervisor_account", - "tags" : [ "account", "avatar", "control", "face", "human", "parental", "parental control", "parents", "people", "person", "profile", "supervised", "supervisor", "user" ] -}, { - "name" : "touch_app", - "tags" : [ "app", "command", "fingers", "gesture", "hand", "press", "tap", "touch" ] -}, { - "name" : "pending", - "tags" : [ "circle", "dots", "loading", "pending", "progress", "wait", "waiting" ] -}, { - "name" : "zoom_in", - "tags" : [ "big", "bigger", "find", "glass", "grow", "in", "look", "magnify", "magnifying", "plus", "scale", "search", "see", "size", "zoom" ] -}, { - "name" : "manage_search", - "tags" : [ "glass", "history", "magnifying", "manage", "search", "text" ] -}, { - "name" : "remove_circle", - "tags" : [ "block", "can", "circle", "delete", "minus", "negative", "remove", "substract", "trash" ] -}, { - "name" : "group_add", - "tags" : [ "accounts", "add", "committee", "face", "family", "friends", "group", "humans", "increase", "more", "network", "people", "persons", "plus", "profiles", "social", "team", "users" ] -}, { - "name" : "chat_bubble_outline", - "tags" : [ "bubble", "chat", "comment", "communicate", "feedback", "message", "outline", "speech" ] -}, { - "name" : "assessment", - "tags" : [ "analytics", "assessment", "bar", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "statistics", "tracking" ] -}, { - "name" : "priority_high", - "tags" : [ "!", "alert", "attention", "caution", "danger", "error", "exclamation", "high", "important", "mark", "notification", "symbol", "warning" ] -}, { - "name" : "push_pin", - "tags" : [ "location", "marker", "pin", "place", "push", "remember", "save" ] -}, { - "name" : "feed", - "tags" : [ "article", "feed", "headline", "information", "news", "newspaper", "paper", "public", "social", "timeline" ] -}, { - "name" : "leaderboard", - "tags" : [ "analytics", "bar", "bars", "chart", "data", "diagram", "graph", "infographic", "leaderboard", "measure", "metrics", "statistics", "tracking" ] -}, { - "name" : "summarize", - "tags" : [ "doc", "document", "list", "menu", "note", "report", "summary" ] -}, { - "name" : "block", - "tags" : [ "avoid", "block", "cancel", "close", "entry", "exit", "no", "prohibited", "quit", "remove", "stop" ] -}, { - "name" : "event_available", - "tags" : [ "approve", "available", "calendar", "check", "complete", "date", "done", "event", "mark", "ok", "schedule", "select", "tick", "time", "validate", "verified", "yes" ] -}, { - "name" : "thumb_up_off_alt", - "tags" : [ "alt", "disabled", "enabled", "favorite", "fingers", "gesture", "hand", "hands", "like", "off", "offline", "on", "rank", "ranking", "rate", "rating", "slash", "thumb", "up" ] -}, { - "name" : "directions_car", - "tags" : [ "automobile", "car", "cars", "direction", "directions", "maps", "public", "transportation", "vehicle" ] -}, { - "name" : "open_in_full", - "tags" : [ "action", "arrow", "arrows", "expand", "full", "grow", "in", "move", "open" ] -}, { - "name" : "auto_stories", - "tags" : [ "auto", "book", "flipping", "pages", "stories" ] -}, { - "name" : "post_add", - "tags" : [ "+", "add", "data", "doc", "document", "drive", "file", "folder", "folders", "page", "paper", "plus", "post", "sheet", "slide", "text", "writing" ] -}, { - "name" : "calculate", - "tags" : [ "+", "-", "=", "calculate", "count", "finance calculator", "math" ] -}, { - "name" : "alternate_email", - "tags" : [ "@", "address", "alternate", "contact", "email", "tag" ] -}, { - "name" : "create", - "tags" : [ "compose", "create", "edit", "editing", "input", "new", "pen", "pencil", "write", "writing" ] -}, { - "name" : "cloud_upload", - "tags" : [ "app", "application", "arrow", "backup", "cloud", "connection", "download", "drive", "files", "folders", "internet", "network", "sky", "storage", "up", "upload" ] -}, { - "name" : "local_fire_department", - "tags" : [ "911", "climate", "department", "fire", "firefighter", "flame", "heat", "home", "hot", "nest", "thermostat" ] -}, { - "name" : "bar_chart", - "tags" : [ "analytics", "bar", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "statistics", "tracking" ] -}, { - "name" : "password", - "tags" : [ "key", "login", "password", "pin", "security", "star", "unlock" ] -}, { - "name" : "collections", - "tags" : [ "album", "collections", "gallery", "image", "landscape", "library", "mountain", "mountains", "photo", "photography", "picture", "stack" ] -}, { - "name" : "preview", - "tags" : [ "design", "eye", "layout", "preview", "reveal", "screen", "see", "show", "site", "view", "web", "website", "window", "www" ] -}, { - "name" : "star_outline", - "tags" : [ "bookmark", "favorite", "half", "highlight", "ranking", "rate", "rating", "save", "star", "toggle" ] -}, { - "name" : "exit_to_app", - "tags" : [ "app", "application", "arrow", "components", "design", "exit", "export", "interface", "layout", "leave", "mobile", "monitor", "move", "output", "phone", "screen", "site", "tablet", "to", "ui", "ux", "web", "website", "window" ] -}, { - "name" : "done_outline", - "tags" : [ "all", "approve", "check", "complete", "done", "mark", "ok", "outline", "select", "tick", "validate", "verified", "yes" ] -}, { - "name" : "psychology", - "tags" : [ "behavior", "body", "brain", "cognitive", "function", "gear", "head", "human", "intellectual", "mental", "mind", "people", "person", "preferences", "psychiatric", "psychology", "science", "settings", "social", "therapy", "thinking", "thoughts" ] -}, { - "name" : "assignment_ind", - "tags" : [ "account", "assignment", "clipboard", "doc", "document", "face", "ind", "people", "person", "profile", "user" ] -}, { - "name" : "volunteer_activism", - "tags" : [ "activism", "donation", "fingers", "gesture", "giving", "hand", "hands", "heart", "love", "sharing", "volunteer" ] -}, { - "name" : "navigate_before", - "tags" : [ "arrow", "arrows", "before", "direction", "left", "navigate" ] -}, { - "name" : "published_with_changes", - "tags" : [ "approve", "arrow", "arrows", "changes", "check", "complete", "done", "inprogress", "load", "loading", "mark", "ok", "published", "refresh", "renew", "replace", "rotate", "select", "tick", "validate", "verified", "with", "yes" ] -}, { - "name" : "add_a_photo", - "tags" : [ "+", "a photo", "add", "camera", "lens", "new", "photography", "picture", "plus", "symbol" ] -}, { - "name" : "auto_awesome", - "tags" : [ "adjust", "ai", "artificial", "automatic", "automation", "custom", "edit", "editing", "enhance", "genai", "intelligence", "magic", "smart", "spark", "sparkle", "star", "stars" ] -}, { - "name" : "card_giftcard", - "tags" : [ "account", "balance", "bill", "card", "cart", "cash", "certificate", "coin", "commerce", "credit", "currency", "dollars", "gift", "giftcard", "money", "online", "pay", "payment", "present", "shopping" ] -}, { - "name" : "fullscreen", - "tags" : [ "adjust", "app", "application", "components", "full", "fullscreen", "interface", "screen", "site", "size", "ui", "ux", "view", "web", "website" ] -}, { - "name" : "sell", - "tags" : [ "bill", "card", "cart", "cash", "coin", "commerce", "credit", "currency", "dollars", "money", "online", "pay", "payment", "price", "sell", "shopping", "tag" ] -}, { - "name" : "checklist", - "tags" : [ "align", "alignment", "approve", "check", "checklist", "complete", "doc", "done", "edit", "editing", "editor", "format", "list", "mark", "notes", "ok", "select", "sheet", "spreadsheet", "text", "tick", "type", "validate", "verified", "writing", "yes" ] -}, { - "name" : "view_in_ar", - "tags" : [ "3d", "ar", "augmented", "cube", "daydream", "headset", "in", "reality", "square", "view", "vr" ] -}, { - "name" : "undo", - "tags" : [ "arrow", "backward", "mail", "previous", "redo", "repeat", "rotate", "undo" ] -}, { - "name" : "arrow_drop_up", - "tags" : [ "app", "application", "arrow", "components", "direction", "drop", "interface", "navigation", "screen", "site", "ui", "up", "ux", "web", "website" ] -}, { - "name" : "feedback", - "tags" : [ "!", "alert", "announcement", "attention", "bubble", "caution", "chat", "comment", "communicate", "danger", "error", "exclamation", "feedback", "important", "mark", "message", "notification", "speech", "symbol", "warning" ] -}, { - "name" : "health_and_safety", - "tags" : [ "+", "add", "and", "certified", "cross", "health", "home", "nest", "plus", "privacy", "private", "protect", "protection", "safety", "security", "shield", "symbol", "verified" ] -}, { - "name" : "work_outline", - "tags" : [ "bag", "baggage", "briefcase", "business", "case", "job", "suitcase", "work" ] -}, { - "name" : "unfold_more", - "tags" : [ "arrow", "arrows", "chevron", "collapse", "direction", "down", "expand", "expandable", "list", "more", "navigation", "unfold" ] -}, { - "name" : "travel_explore", - "tags" : [ "earth", "explore", "find", "glass", "global", "globe", "look", "magnify", "magnifying", "map", "network", "planet", "search", "see", "social", "space", "travel", "web", "world" ] -}, { - "name" : "palette", - "tags" : [ "art", "color", "colors", "filters", "paint", "palette" ] -}, { - "name" : "keyboard_arrow_right", - "tags" : [ "arrow", "arrows", "keyboard", "right" ] -}, { - "name" : "double_arrow", - "tags" : [ "arrow", "arrows", "direction", "double", "multiple", "navigation", "right" ] -}, { - "name" : "computer", - "tags" : [ "Android", "OS", "chrome", "computer", "desktop", "device", "hardware", "iOS", "mac", "monitor", "web", "window" ] -}, { - "name" : "timeline", - "tags" : [ "data", "history", "line", "movement", "point", "points", "timeline", "tracking", "trending", "zigzag" ] -}, { - "name" : "thumb_up_alt", - "tags" : [ "agreed", "approved", "confirm", "correct", "favorite", "feedback", "good", "happy", "like", "okay", "positive", "satisfaction", "social", "thumb", "up", "vote", "yes" ] -}, { - "name" : "signal_cellular_alt", - "tags" : [ "alt", "analytics", "bar", "cell", "cellular", "chart", "data", "diagram", "graph", "infographic", "internet", "measure", "metrics", "mobile", "network", "phone", "signal", "statistics", "tracking", "wifi", "wireless" ] -}, { - "name" : "replay", - "tags" : [ "arrow", "arrows", "control", "controls", "music", "refresh", "renew", "repeat", "replay", "video" ] -}, { - "name" : "swap_horiz", - "tags" : [ "arrow", "arrows", "back", "forward", "horizontal", "swap" ] -}, { - "name" : "volume_off", - "tags" : [ "audio", "control", "disabled", "enabled", "low", "music", "off", "on", "slash", "sound", "speaker", "tv", "volume" ] -}, { - "name" : "forum", - "tags" : [ "bubble", "chat", "comment", "communicate", "community", "conversation", "feedback", "forum", "hub", "message", "speech" ] -}, { - "name" : "skip_next", - "tags" : [ "arrow", "control", "controls", "music", "next", "play", "previous", "skip", "video" ] -}, { - "name" : "water_drop", - "tags" : [ "drink", "drop", "droplet", "eco", "liquid", "nature", "ocean", "rain", "social", "water" ] -}, { - "name" : "assignment_turned_in", - "tags" : [ "approve", "assignment", "check", "clipboard", "complete", "doc", "document", "done", "in", "mark", "ok", "select", "tick", "turn", "validate", "verified", "yes" ] -}, { - "name" : "library_books", - "tags" : [ "add", "album", "audio", "book", "books", "collection", "library", "read", "reading" ] -}, { - "name" : "maps_home_work", - "tags" : [ "building", "home", "house", "maps", "office", "work" ] -}, { - "name" : "dns", - "tags" : [ "address", "bars", "dns", "domain", "information", "ip", "list", "lookup", "name", "server", "system" ] -}, { - "name" : "sync_alt", - "tags" : [ "alt", "arrow", "arrows", "horizontal", "internet", "sync", "technology", "up", "update", "wifi" ] -}, { - "name" : "how_to_reg", - "tags" : [ "approve", "ballot", "check", "complete", "done", "election", "how", "mark", "ok", "poll", "register", "registration", "select", "tick", "to reg", "validate", "verified", "vote", "yes" ] -}, { - "name" : "notifications_none", - "tags" : [ "alarm", "alert", "bell", "none", "notifications", "notify", "reminder", "sound" ] -}, { - "name" : "stars", - "tags" : [ "achievement", "bookmark", "circle", "favorite", "highlight", "important", "marked", "ranking", "rate", "rating rank", "reward", "save", "saved", "shape", "special", "star" ] -}, { - "name" : "flight_takeoff", - "tags" : [ "airport", "departed", "departing", "flight", "fly", "landing", "plane", "takeoff", "transportation", "travel" ] -}, { - "name" : "label", - "tags" : [ "favorite", "indent", "label", "library", "mail", "remember", "save", "stamp", "sticker", "tag" ] -}, { - "name" : "devices", - "tags" : [ "Android", "OS", "computer", "desktop", "device", "hardware", "iOS", "laptop", "mobile", "monitor", "phone", "tablet", "watch", "wearable", "web" ] -}, { - "name" : "chat_bubble", - "tags" : [ "bubble", "chat", "comment", "communicate", "feedback", "message", "speech" ] -}, { - "name" : "emoji_emotions", - "tags" : [ "+", "add", "emoji", "emotions", "expressions", "face", "feelings", "glad", "happiness", "happy", "icon", "icons", "insert", "like", "mood", "new", "person", "pleased", "plus", "smile", "smiling", "social", "survey", "symbol" ] -}, { - "name" : "remove_red_eye", - "tags" : [ "eye", "iris", "look", "looking", "preview", "red", "remove", "see", "sight", "vision" ] -}, { - "name" : "content_paste", - "tags" : [ "clipboard", "content", "copy", "cut", "doc", "document", "file", "multiple", "past" ] -}, { - "name" : "folder_open", - "tags" : [ "data", "doc", "document", "drive", "file", "folder", "folders", "open", "sheet", "slide", "storage" ] -}, { - "name" : "text_snippet", - "tags" : [ "data", "doc", "document", "file", "note", "notes", "snippet", "storage", "text", "writing" ] -}, { - "name" : "tips_and_updates", - "tags" : [ "ai", "alert", "and", "announcement", "artificial", "automatic", "automation", "custom", "electricity", "genai", "idea", "info", "information", "intelligence", "light", "lightbulb", "magic", "smart", "spark", "sparkle", "star", "tips", "updates" ] -}, { - "name" : "my_location", - "tags" : [ "destination", "direction", "location", "maps", "navigation", "pin", "place", "point", "stop" ] -}, { - "name" : "textsms", - "tags" : [ "bubble", "chat", "comment", "communicate", "dots", "feedback", "message", "speech", "textsms" ] -}, { - "name" : "cloud", - "tags" : [ "cloud", "connection", "internet", "network", "sky", "upload" ] -}, { - "name" : "sports_esports", - "tags" : [ "controller", "entertainment", "esports", "game", "gamepad", "gaming", "hobby", "online", "social", "sports", "video" ] -}, { - "name" : "security", - "tags" : [ "certified", "privacy", "private", "protect", "protection", "security", "shield", "verified" ] -}, { - "name" : "request_quote", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "money", "online", "pay", "payment", "price", "quote", "request", "shopping", "symbol" ] -}, { - "name" : "toggle_off", - "tags" : [ "active", "app", "application", "components", "configuration", "control", "design", "disable", "inable", "inactive", "interface", "off", "on", "selection", "settings", "site", "slider", "switch", "toggle", "ui", "ux", "web", "website" ] -}, { - "name" : "book", - "tags" : [ "book", "bookmark", "favorite", "label", "library", "read", "reading", "remember", "ribbon", "save", "tag" ] -}, { - "name" : "contact_page", - "tags" : [ "account", "avatar", "contact", "data", "doc", "document", "drive", "face", "file", "folder", "folders", "human", "page", "people", "person", "profile", "sheet", "slide", "storage", "user", "writing" ] -}, { - "name" : "speed", - "tags" : [ "arrow", "control", "controls", "fast", "gauge", "meter", "motion", "music", "slow", "speed", "speedometer", "velocity", "video" ] -}, { - "name" : "bug_report", - "tags" : [ "animal", "bug", "fix", "insect", "issue", "problem", "report", "testing", "virus", "warning" ] -}, { - "name" : "space_dashboard", - "tags" : [ "cards", "dashboard", "format", "grid", "layout", "rectangle", "shapes", "space", "squares", "web", "website" ] -}, { - "name" : "fiber_manual_record", - "tags" : [ "circle", "dot", "fiber", "manual", "play", "record", "watch" ] -}, { - "name" : "report", - "tags" : [ "!", "alert", "attention", "caution", "danger", "error", "exclamation", "important", "mark", "notification", "octagon", "report", "symbol", "warning" ] -}, { - "name" : "alarm", - "tags" : [ "alarm", "alert", "bell", "clock", "countdown", "date", "notification", "schedule", "time" ] -}, { - "name" : "cached", - "tags" : [ "around", "arrows", "cache", "cached", "inprogress", "load", "loading refresh", "renew", "rotate" ] -}, { - "name" : "translate", - "tags" : [ "language", "speaking", "speech", "translate", "translator", "words" ] -}, { - "name" : "pan_tool", - "tags" : [ "fingers", "gesture", "hand", "hands", "human", "move", "pan", "scan", "stop", "tool" ] -}, { - "name" : "gavel", - "tags" : [ "agreement", "contract", "court", "document", "gavel", "government", "judge", "law", "mallet", "official", "police", "rule", "rules", "terms" ] -}, { - "name" : "settings_suggest", - "tags" : [ "ai", "artificial", "automatic", "automation", "change", "custom", "details", "gear", "genai", "intelligence", "magic", "options", "recommendation", "service", "settings", "smart", "spark", "sparkle", "star", "suggest", "suggestion", "system" ] -}, { - "name" : "file_copy", - "tags" : [ "content", "copy", "cut", "doc", "document", "duplicate", "file", "multiple", "past" ] -}, { - "name" : "edit_calendar", - "tags" : [ "calendar", "compose", "create", "date", "day", "draft", "edit", "editing", "event", "month", "pen", "pencil", "schedule", "write", "writing" ] -}, { - "name" : "contact_mail", - "tags" : [ "account", "address", "avatar", "communicate", "contact", "email", "face", "human", "info", "information", "mail", "message", "people", "person", "profile", "user" ] -}, { - "name" : "quiz", - "tags" : [ "?", "assistance", "faq", "help", "info", "information", "punctuation", "question mark", "quiz", "support", "symbol", "test" ] -}, { - "name" : "supervised_user_circle", - "tags" : [ "account", "avatar", "circle", "control", "face", "human", "parental", "parents", "people", "person", "profile", "supervised", "supervisor", "user" ] -}, { - "name" : "cloud_download", - "tags" : [ "app", "application", "arrow", "backup", "cloud", "connection", "down", "download", "drive", "files", "folders", "internet", "network", "sky", "storage", "upload" ] -}, { - "name" : "stop", - "tags" : [ "control", "controls", "music", "pause", "play", "square", "stop", "video" ] -}, { - "name" : "person_search", - "tags" : [ "account", "avatar", "face", "find", "glass", "human", "look", "magnify", "magnifying", "people", "person", "profile", "search", "user" ] -}, { - "name" : "location_city", - "tags" : [ "apartments", "architecture", "buildings", "business", "city", "estate", "home", "landscape", "location", "place", "real", "residence", "residential", "shelter", "town", "urban" ] -}, { - "name" : "sentiment_very_satisfied", - "tags" : [ "emotions", "expressions", "face", "feelings", "glad", "happiness", "happy", "like", "mood", "person", "pleased", "satisfied", "sentiment", "smile", "smiling", "survey", "very" ] -}, { - "name" : "ios_share", - "tags" : [ "arrow", "export", "ios", "send", "share", "up" ] -}, { - "name" : "minimize", - "tags" : [ "app", "application", "components", "design", "interface", "line", "minimize", "screen", "shape", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "qr_code", - "tags" : [ "barcode", "camera", "code", "media", "product", "qr", "quick", "response", "smartphone", "url", "urls" ] -}, { - "name" : "sentiment_satisfied_alt", - "tags" : [ "account", "alt", "emoji", "face", "happy", "human", "people", "person", "profile", "satisfied", "sentiment", "smile", "user" ] -}, { - "name" : "local_mall", - "tags" : [ "bag", "bill", "building", "business", "buy", "card", "cart", "cash", "coin", "commerce", "credit", "currency", "dollars", "handbag", "local", "mall", "money", "online", "pay", "payment", "shop", "shopping", "store", "storefront" ] -}, { - "name" : "qr_code_2", - "tags" : [ "barcode", "camera", "code", "media", "product", "qr", "quick", "response", "smartphone", "url", "urls" ] -}, { - "name" : "flight", - "tags" : [ "air", "airplane", "airport", "flight", "plane", "transportation", "travel", "trip" ] -}, { - "name" : "desktop_windows", - "tags" : [ "Android", "OS", "chrome", "desktop", "device", "display", "hardware", "iOS", "mac", "monitor", "screen", "television", "tv", "web", "window", "windows" ] -}, { - "name" : "music_note", - "tags" : [ "audio", "audiotrack", "key", "music", "note", "sound", "track" ] -}, { - "name" : "sentiment_satisfied", - "tags" : [ "emotions", "expressions", "face", "feelings", "glad", "happiness", "happy", "like", "mood", "person", "pleased", "satisfied", "sentiment", "smile", "smiling", "survey" ] -}, { - "name" : "android", - "tags" : [ "android", "character", "logo", "mascot", "toy" ] -}, { - "name" : "accessibility", - "tags" : [ "accessibility", "accessible", "body", "handicap", "help", "human", "people", "person" ] -}, { - "name" : "backspace", - "tags" : [ "arrow", "back", "backspace", "cancel", "clear", "correct", "delete", "erase", "remove" ] -}, { - "name" : "precision_manufacturing", - "tags" : [ "arm", "automatic", "chain", "conveyor", "crane", "factory", "industry", "machinery", "manufacturing", "mechanical", "precision", "production", "repairing", "robot", "supply", "warehouse" ] -}, { - "name" : "drag_handle", - "tags" : [ "app", "application ui", "components", "design", "drag", "handle", "interface", "layout", "menu", "move", "screen", "site", "ui", "ux", "web", "website", "window" ] -}, { - "name" : "smart_display", - "tags" : [ "airplay", "cast", "chrome", "connect", "device", "display", "play", "screen", "screencast", "smart", "stream", "television", "tv", "video", "wireless" ] -}, { - "name" : "near_me", - "tags" : [ "destination", "direction", "location", "maps", "me", "navigation", "near", "pin", "place", "point", "stop" ] -}, { - "name" : "west", - "tags" : [ "arrow", "directional", "left", "maps", "navigation", "west" ] -}, { - "name" : "get_app", - "tags" : [ "app", "arrow", "arrows", "down", "download", "downloads", "export", "get", "install", "play", "upload" ] -}, { - "name" : "person_add_alt", - "tags" : [ "+", "account", "add", "face", "human", "people", "person", "plus", "profile", "user" ] -}, { - "name" : "fitness_center", - "tags" : [ "athlete", "center", "dumbbell", "exercise", "fitness", "gym", "hobby", "places", "sport", "weights", "workout" ] -}, { - "name" : "shield", - "tags" : [ "certified", "privacy", "private", "protect", "protection", "security", "shield", "verified" ] -}, { - "name" : "message", - "tags" : [ "bubble", "chat", "comment", "communicate", "feedback", "message", "speech" ] -}, { - "name" : "rocket_launch", - "tags" : [ "launch", "rocket", "space", "spaceship", "takeoff" ] -}, { - "name" : "record_voice_over", - "tags" : [ "account", "face", "human", "over", "people", "person", "profile", "record", "recording", "speak", "speaking", "speech", "transcript", "user", "voice" ] -}, { - "name" : "add_task", - "tags" : [ "+", "add", "approve", "check", "circle", "completed", "increase", "mark", "ok", "plus", "select", "task", "tick", "yes" ] -}, { - "name" : "drive_file_rename_outline", - "tags" : [ "compose", "create", "draft", "drive", "edit", "editing", "file", "input", "marker", "pen", "pencil", "rename", "write", "writing" ] -}, { - "name" : "insert_drive_file", - "tags" : [ "doc", "drive", "file", "format", "insert", "sheet", "slide" ] -}, { - "name" : "question_mark", - "tags" : [ "?", "assistance", "help", "info", "information", "punctuation", "question mark", "support", "symbol" ] -}, { - "name" : "trending_flat", - "tags" : [ "arrow", "change", "data", "flat", "metric", "movement", "rate", "right", "track", "tracking", "trending" ] -}, { - "name" : "handyman", - "tags" : [ "build", "construction", "fix", "hammer", "handyman", "repair", "screw", "screwdriver", "tools" ] -}, { - "name" : "emoji_objects", - "tags" : [ "bulb", "creative", "emoji", "idea", "light", "objects", "solution", "thinking" ] -}, { - "name" : "military_tech", - "tags" : [ "army", "award", "badge", "honor", "medal", "merit", "military", "order", "privilege", "prize", "rank", "reward", "ribbon", "soldier", "star", "status", "tech", "trophy", "win", "winner" ] -}, { - "name" : "hourglass_empty", - "tags" : [ "countdown", "empty", "hourglass", "loading", "minutes", "time", "wait", "waiting" ] -}, { - "name" : "help_center", - "tags" : [ "?", "assistance", "center", "help", "info", "information", "punctuation", "question mark", "recent", "restore", "support", "symbol" ] -}, { - "name" : "science", - "tags" : [ "beaker", "chemical", "chemistry", "experiment", "flask", "glass", "laboratory", "research", "science", "tube" ] -}, { - "name" : "storage", - "tags" : [ "computer", "data", "drive", "memory", "storage" ] -}, { - "name" : "movie", - "tags" : [ "cinema", "film", "media", "movie", "slate", "video" ] -}, { - "name" : "accessibility_new", - "tags" : [ "accessibility", "accessible", "body", "handicap", "help", "human", "new", "people", "person" ] -}, { - "name" : "workspace_premium", - "tags" : [ "certification", "degree", "ecommerce", "guarantee", "medal", "permit", "premium", "ribbon", "verification", "workspace" ] -}, { - "name" : "directions_run", - "tags" : [ "body", "directions", "human", "jogging", "maps", "people", "person", "route", "run", "running", "walk" ] -}, { - "name" : "rule", - "tags" : [ "approve", "check", "complete", "done", "incomplete", "line", "mark", "missing", "no", "ok", "rule", "select", "tick", "validate", "verified", "wrong", "x", "yes" ] -}, { - "name" : "thumb_down", - "tags" : [ "ate", "dislike", "down", "favorite", "fingers", "gesture", "hand", "hands", "like", "rank", "ranking", "rating", "thumb" ] -}, { - "name" : "event_note", - "tags" : [ "calendar", "date", "event", "note", "schedule", "text", "time", "writing" ] -}, { - "name" : "contacts", - "tags" : [ "account", "avatar", "call", "cell", "contacts", "face", "human", "info", "information", "mobile", "people", "person", "phone", "profile", "user" ] -}, { - "name" : "comment", - "tags" : [ "bubble", "chat", "comment", "communicate", "feedback", "message", "outline", "speech" ] -}, { - "name" : "restaurant_menu", - "tags" : [ "book", "dining", "eat", "food", "fork", "knife", "local", "meal", "menu", "restaurant", "spoon" ] -}, { - "name" : "add_photo_alternate", - "tags" : [ "+", "add", "alternate", "image", "landscape", "mountain", "mountains", "new", "photo", "photography", "picture", "plus", "symbol" ] -}, { - "name" : "confirmation_number", - "tags" : [ "admission", "confirmation", "entertainment", "event", "number", "ticket" ] -}, { - "name" : "sticky_note_2", - "tags" : [ "2", "bookmark", "mark", "message", "note", "paper", "sticky", "text", "writing" ] -}, { - "name" : "format_quote", - "tags" : [ "doc", "edit", "editing", "editor", "format", "quotation", "quote", "sheet", "spreadsheet", "text", "type", "writing" ] -}, { - "name" : "history_edu", - "tags" : [ "document", "edu", "education", "feather", "history", "letter", "paper", "pen", "quill", "school", "story", "tools", "write", "writing" ] -}, { - "name" : "business_center", - "tags" : [ "bag", "baggage", "briefcase", "business", "case", "center", "places", "purse", "suitcase", "work" ] -}, { - "name" : "upload", - "tags" : [ "arrow", "arrows", "download", "drive", "up", "upload" ] -}, { - "name" : "skip_previous", - "tags" : [ "arrow", "control", "controls", "music", "next", "play", "previous", "skip", "video" ] -}, { - "name" : "archive", - "tags" : [ "archive", "inbox", "mail", "store" ] -}, { - "name" : "wb_sunny", - "tags" : [ "balance", "bright", "light", "lighting", "sun", "sunny", "wb", "white" ] -}, { - "name" : "cake", - "tags" : [ "add", "baked", "birthday", "cake", "candles", "celebration", "dessert", "food", "frosting", "new", "party", "pastries", "pastry", "plus", "social", "sweet", "symbol" ] -}, { - "name" : "attachment", - "tags" : [ "attach", "attachment", "clip", "compose", "file", "image", "link" ] -}, { - "name" : "source", - "tags" : [ "code", "composer", "content", "creation", "data", "doc", "document", "file", "folder", "mode", "source", "storage", "view" ] -}, { - "name" : "settings_applications", - "tags" : [ "application", "change", "details", "gear", "info", "information", "options", "personal", "service", "settings" ] -}, { - "name" : "dashboard_customize", - "tags" : [ "cards", "customize", "dashboard", "format", "layout", "rectangle", "shapes", "square", "web", "website" ] -}, { - "name" : "find_in_page", - "tags" : [ "data", "doc", "document", "drive", "file", "find", "folder", "folders", "glass", "in", "look", "magnify", "magnifying", "page", "paper", "search", "see", "sheet", "slide", "writing" ] -}, { - "name" : "support", - "tags" : [ "assist", "buoy", "help", "life", "lifebuoy", "rescue", "safe", "safety", "support" ] -}, { - "name" : "ads_click", - "tags" : [ "ads", "browser", "click", "clicks", "cursor", "internet", "target", "traffic", "web" ] -}, { - "name" : "new_releases", - "tags" : [ "approve", "award", "check", "checkmark", "complete", "done", "new", "notification", "ok", "release", "releases", "select", "star", "symbol", "tick", "verification", "verified", "warning", "yes" ] -}, { - "name" : "flutter_dash", - "tags" : [ "bird", "dash", "flutter", "mascot" ] -}, { - "name" : "playlist_add", - "tags" : [ "+", "add", "collection", "list", "music", "new", "playlist", "plus", "symbol" ] -}, { - "name" : "save_alt", - "tags" : [ "alt", "arrow", "disk", "document", "down", "file", "floppy", "multimedia", "save" ] -}, { - "name" : "close_fullscreen", - "tags" : [ "action", "arrow", "arrows", "close", "collapse", "direction", "full", "fullscreen", "minimize", "screen" ] -}, { - "name" : "credit_score", - "tags" : [ "approve", "bill", "card", "cash", "check", "coin", "commerce", "complete", "cost", "credit", "currency", "dollars", "done", "finance", "loan", "mark", "money", "ok", "online", "pay", "payment", "score", "select", "symbol", "tick", "validate", "verified", "yes" ] -}, { - "name" : "layers", - "tags" : [ "arrange", "disabled", "enabled", "interaction", "layers", "maps", "off", "on", "overlay", "pages", "slash" ] -}, { - "name" : "redeem", - "tags" : [ "bill", "card", "cart", "cash", "certificate", "coin", "commerce", "credit", "currency", "dollars", "gift", "giftcard", "money", "online", "pay", "payment", "present", "redeem", "shopping" ] -}, { - "name" : "spa", - "tags" : [ "aromatherapy", "flower", "healthcare", "leaf", "massage", "meditation", "nature", "petals", "places", "relax", "spa", "wellbeing", "wellness" ] -}, { - "name" : "announcement", - "tags" : [ "!", "alert", "announcement", "attention", "bubble", "caution", "chat", "comment", "communicate", "danger", "error", "exclamation", "feedback", "important", "mark", "message", "notification", "speech", "symbol", "warning" ] -}, { - "name" : "keyboard_backspace", - "tags" : [ "arrow", "back", "backspace", "keyboard", "left" ] -}, { - "name" : "loyalty", - "tags" : [ "benefits", "card", "credit", "heart", "loyalty", "membership", "miles", "points", "program", "subscription", "tag", "travel", "trip" ] -}, { - "name" : "swap_vert", - "tags" : [ "arrow", "arrows", "direction", "down", "navigation", "swap", "up", "vert", "vertical" ] -}, { - "name" : "sentiment_dissatisfied", - "tags" : [ "angry", "disappointed", "dislike", "dissatisfied", "emotions", "expressions", "face", "feelings", "frown", "mood", "person", "sad", "sentiment", "survey", "unhappy", "unsatisfied", "upset" ] -}, { - "name" : "medical_services", - "tags" : [ "aid", "bag", "briefcase", "emergency", "first", "kit", "medical", "medicine", "services" ] -}, { - "name" : "view_headline", - "tags" : [ "design", "format", "grid", "headline", "layout", "paragraph", "text", "view", "website" ] -}, { - "name" : "arrow_circle_right", - "tags" : [ "arrow", "circle", "direction", "navigation", "right" ] -}, { - "name" : "format_list_numbered", - "tags" : [ "align", "alignment", "digit", "doc", "edit", "editing", "editor", "format", "list", "notes", "number", "numbered", "sheet", "spreadsheet", "symbol", "text", "type", "writing" ] -}, { - "name" : "phone_android", - "tags" : [ "OS", "android", "cell", "device", "hardware", "iOS", "mobile", "phone", "tablet" ] -}, { - "name" : "sms", - "tags" : [ "3", "bubble", "chat", "communication", "conversation", "dots", "message", "more", "service", "sms", "speech", "three" ] -}, { - "name" : "restore", - "tags" : [ "arrow", "back", "backwards", "clock", "date", "history", "refresh", "renew", "restore", "reverse", "rotate", "schedule", "time", "turn" ] -}, { - "name" : "policy", - "tags" : [ "certified", "find", "glass", "legal", "look", "magnify", "magnifying", "policy", "privacy", "private", "protect", "protection", "search", "security", "see", "shield", "verified" ] -}, { - "name" : "dangerous", - "tags" : [ "broken", "danger", "dangerous", "fix", "no", "sign", "stop", "update", "warning", "wrong", "x" ] -}, { - "name" : "battery_full", - "tags" : [ "battery", "cell", "charge", "full", "mobile", "power" ] -}, { - "name" : "euro_symbol", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "euro", "finance", "money", "online", "pay", "payment", "symbol" ] -}, { - "name" : "query_stats", - "tags" : [ "analytics", "chart", "data", "diagram", "find", "glass", "graph", "infographic", "line", "look", "magnify", "magnifying", "measure", "metrics", "query", "search", "see", "statistics", "stats", "tracking" ] -}, { - "name" : "group_work", - "tags" : [ "alliance", "collaboration", "group", "partnership", "team", "teamwork", "together", "work" ] -}, { - "name" : "expand_circle_down", - "tags" : [ "arrow", "arrows", "chevron", "circle", "collapse", "direction", "down", "expand", "expandable", "list", "more" ] -}, { - "name" : "sensors", - "tags" : [ "connection", "network", "scan", "sensors", "signal", "wireless" ] -}, { - "name" : "keyboard_arrow_up", - "tags" : [ "arrow", "arrows", "keyboard", "up" ] -}, { - "name" : "brush", - "tags" : [ "art", "brush", "design", "draw", "edit", "editing", "paint", "painting", "tool" ] -}, { - "name" : "meeting_room", - "tags" : [ "building", "door", "doorway", "entrance", "home", "house", "interior", "meeting", "office", "open", "places", "room" ] -}, { - "name" : "key", - "tags" : [ "key", "lock", "password", "unlock" ] -}, { - "name" : "house", - "tags" : [ "architecture", "building", "estate", "family", "home", "homepage", "house", "place", "places", "real", "residence", "residential", "shelter" ] -}, { - "name" : "lunch_dining", - "tags" : [ "breakfast", "dining", "dinner", "drink", "fastfood", "food", "hamburger", "lunch", "meal" ] -}, { - "name" : "table_chart", - "tags" : [ "analytics", "bar", "bars", "chart", "data", "diagram", "graph", "infographic grid", "measure", "metrics", "statistics", "table", "tracking" ] -}, { - "name" : "border_color", - "tags" : [ "all", "border", "doc", "edit", "editing", "editor", "pen", "pencil", "sheet", "spreadsheet", "stroke", "text", "type", "writing" ] -}, { - "name" : "compare_arrows", - "tags" : [ "arrow", "arrows", "collide", "compare", "direction", "left", "pressure", "push", "right", "together" ] -}, { - "name" : "south", - "tags" : [ "arrow", "directional", "down", "maps", "navigation", "south" ] -}, { - "name" : "directions_walk", - "tags" : [ "body", "direction", "directions", "human", "jogging", "maps", "people", "person", "route", "run", "walk" ] -}, { - "name" : "arrow_left", - "tags" : [ "app", "application", "arrow", "components", "direction", "interface", "left", "navigation", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "tag", - "tags" : [ "hash", "hashtag", "key", "media", "number", "pound", "social", "tag", "trend" ] -}, { - "name" : "change_circle", - "tags" : [ "around", "arrows", "change", "circle", "direction", "navigation", "rotate" ] -}, { - "name" : "subject", - "tags" : [ "alignment", "doc", "document", "email", "full", "justify", "list", "note", "subject", "text", "writing" ] -}, { - "name" : "sentiment_very_dissatisfied", - "tags" : [ "angry", "disappointed", "dislike", "dissatisfied", "emotions", "expressions", "face", "feelings", "mood", "person", "sad", "sentiment", "sorrow", "survey", "unhappy", "unsatisfied", "upset", "very" ] -}, { - "name" : "local_hospital", - "tags" : [ "911", "aid", "cross", "emergency", "first", "hospital", "local", "medicine" ] -}, { - "name" : "table_view", - "tags" : [ "format", "grid", "group", "layout", "multiple", "table", "view" ] -}, { - "name" : "disabled_by_default", - "tags" : [ "box", "by", "cancel", "close", "default", "disabled", "exit", "no", "quit", "remove", "square", "stop", "x" ] -}, { - "name" : "notification_important", - "tags" : [ "!", "active", "alarm", "alert", "attention", "bell", "caution", "chime", "danger", "error", "exclamation", "important", "mark", "notification", "notifications", "notify", "reminder", "ring", "sound", "symbol", "warning" ] -}, { - "name" : "celebration", - "tags" : [ "activity", "birthday", "celebration", "event", "fun", "party" ] -}, { - "name" : "laptop", - "tags" : [ "Android", "OS", "chrome", "computer", "desktop", "device", "hardware", "iOS", "laptop", "mac", "monitor", "web", "windows" ] -}, { - "name" : "loop", - "tags" : [ "around", "arrow", "arrows", "direction", "inprogress", "load", "loading refresh", "loop", "music", "navigation", "renew", "rotate", "turn" ] -}, { - "name" : "nightlight_round", - "tags" : [ "dark", "half", "light", "mode", "moon", "night", "nightlight", "round" ] -}, { - "name" : "privacy_tip", - "tags" : [ "alert", "announcement", "assistance", "certified", "details", "help", "i", "info", "information", "privacy", "private", "protect", "protection", "security", "service", "shield", "support", "tip", "verified" ] -}, { - "name" : "import_contacts", - "tags" : [ "address", "book", "contacts", "import", "info", "information", "open" ] -}, { - "name" : "equalizer", - "tags" : [ "adjustment", "analytics", "chart", "data", "equalizer", "graph", "measure", "metrics", "music", "noise", "sound", "static", "statistics", "tracking", "volume" ] -}, { - "name" : "app_registration", - "tags" : [ "app", "apps", "edit", "pencil", "register", "registration" ] -}, { - "name" : "keyboard_double_arrow_right", - "tags" : [ "arrow", "arrows", "direction", "double", "multiple", "navigation", "right" ] -}, { - "name" : "handshake", - "tags" : [ "agreement", "hand", "hands", "partnership", "shake" ] -}, { - "name" : "corporate_fare", - "tags" : [ "architecture", "building", "business", "corporate", "estate", "fare", "organization", "place", "real", "residence", "residential", "shelter" ] -}, { - "name" : "local_library", - "tags" : [ "book", "community learning", "library", "local", "read" ] -}, { - "name" : "https", - "tags" : [ "https", "lock", "locked", "password", "privacy", "private", "protection", "safety", "secure", "security" ] -}, { - "name" : "euro", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "euro", "euros", "finance", "money", "online", "pay", "payment", "price", "shopping", "symbol" ] -}, { - "name" : "coronavirus", - "tags" : [ "19", "bacteria", "coronavirus", "covid", "disease", "germs", "illness", "sick", "social" ] -}, { - "name" : "price_check", - "tags" : [ "approve", "bill", "card", "cash", "check", "coin", "commerce", "complete", "cost", "credit", "currency", "dollars", "done", "finance", "mark", "money", "ok", "online", "pay", "payment", "price", "select", "shopping", "symbol", "tick", "validate", "verified", "yes" ] -}, { - "name" : "live_tv", - "tags" : [ "Android", "OS", "antennas hardware", "chrome", "desktop", "device", "iOS", "live", "mac", "monitor", "movie", "play", "stream", "television", "tv", "web", "window" ] -}, { - "name" : "park", - "tags" : [ "attraction", "fresh", "local", "nature", "outside", "park", "plant", "tree" ] -}, { - "name" : "toc", - "tags" : [ "content", "format", "lines", "list", "order", "reorder", "stacked", "table", "title", "titles", "toc" ] -}, { - "name" : "track_changes", - "tags" : [ "bullseye", "changes", "circle", "evolve", "lines", "movement", "rotate", "shift", "target", "track" ] -}, { - "name" : "arrow_circle_up", - "tags" : [ "arrow", "circle", "direction", "navigation", "up" ] -}, { - "name" : "emoji_people", - "tags" : [ "arm", "body", "emoji", "greeting", "human", "people", "person", "social", "waving" ] -}, { - "name" : "flash_on", - "tags" : [ "bolt", "disabled", "electric", "enabled", "fast", "flash", "lightning", "off", "on", "slash", "thunderbolt" ] -}, { - "name" : "copyright", - "tags" : [ "alphabet", "c", "character", "copyright", "emblem", "font", "legal", "letter", "owner", "symbol", "text" ] -}, { - "name" : "bookmarks", - "tags" : [ "bookmark", "bookmarks", "favorite", "label", "layers", "library", "multiple", "read", "reading", "remember", "ribbon", "save", "stack", "tag" ] -}, { - "name" : "ac_unit", - "tags" : [ "ac", "air", "cold", "conditioner", "flake", "snow", "temperature", "unit", "weather", "winter" ] -}, { - "name" : "contact_phone", - "tags" : [ "account", "avatar", "call", "communicate", "contact", "face", "human", "info", "information", "message", "mobile", "people", "person", "phone", "profile", "user" ] -}, { - "name" : "keyboard_arrow_left", - "tags" : [ "arrow", "arrows", "keyboard", "left" ] -}, { - "name" : "medication", - "tags" : [ "doctor", "drug", "emergency", "hospital", "medication", "medicine", "pharmacy", "pills", "prescription" ] -}, { - "name" : "grading", - "tags" : [ "'favorite'_new'. ' Remove this icon & keep 'star'.", "'star_boarder'", "'star_border_purple500'", "'star_outline'", "'star_purple500'", "'star_rate'", "Same as 'star'" ] -}, { - "name" : "keyboard_return", - "tags" : [ "arrow", "back", "keyboard", "left", "return" ] -}, { - "name" : "api", - "tags" : [ "api", "developer", "development", "enterprise", "software" ] -}, { - "name" : "smart_toy", - "tags" : [ "bot", "droid", "games", "robot", "smart", "toy" ] -}, { - "name" : "input", - "tags" : [ "arrow", "box", "download", "input", "login", "move", "right" ] -}, { - "name" : "self_improvement", - "tags" : [ "body", "calm", "care", "chi", "human", "improvement", "meditate", "meditation", "people", "person", "relax", "self", "sitting", "wellbeing", "yoga", "zen" ] -}, { - "name" : "live_help", - "tags" : [ "?", "assistance", "bubble", "chat", "comment", "communicate", "help", "info", "information", "live", "message", "punctuation", "question mark", "recent", "restore", "speech", "support", "symbol" ] -}, { - "name" : "query_builder", - "tags" : [ "builder", "clock", "date", "query", "schedule", "time" ] -}, { - "name" : "perm_media", - "tags" : [ "collection", "data", "doc", "document", "file", "folder", "folders", "image", "landscape", "media", "mountain", "mountains", "perm", "photo", "photography", "picture", "storage" ] -}, { - "name" : "download_for_offline", - "tags" : [ "arrow", "circle", "down", "download", "for offline", "install", "upload" ] -}, { - "name" : "view_module", - "tags" : [ "design", "format", "grid", "layout", "module", "square", "squares", "stacked", "view", "website" ] -}, { - "name" : "pin", - "tags" : [ "1", "2", "3", "digit", "key", "login", "logout", "number", "password", "pattern", "pin", "security", "star", "symbol", "unlock" ] -}, { - "name" : "fast_forward", - "tags" : [ "control", "fast", "forward", "media", "music", "play", "speed", "time", "tv", "video" ] -}, { - "name" : "forward_to_inbox", - "tags" : [ "arrow", "arrows", "directions", "email", "envelop", "forward", "inbox", "letter", "mail", "message", "navigation", "outgoing", "right", "send", "to" ] -}, { - "name" : "person_remove", - "tags" : [ "account", "avatar", "delete", "face", "human", "minus", "people", "person", "profile", "remove", "unfriend", "user" ] -}, { - "name" : "local_atm", - "tags" : [ "atm", "bill", "card", "cart", "cash", "coin", "commerce", "credit", "currency", "dollars", "local", "money", "online", "pay", "payment", "shopping", "symbol" ] -}, { - "name" : "star_half", - "tags" : [ "achievement", "bookmark", "favorite", "half", "highlight", "important", "marked", "ranking", "rate", "rating rank", "reward", "save", "saved", "shape", "special", "star", "toggle" ] -}, { - "name" : "build_circle", - "tags" : [ "adjust", "build", "circle", "fix", "repair", "tool", "wrench" ] -}, { - "name" : "redo", - "tags" : [ "arrow", "backward", "forward", "next", "redo", "repeat", "rotate", "undo" ] -}, { - "name" : "web", - "tags" : [ "browser", "internet", "page", "screen", "site", "web", "website", "www" ] -}, { - "name" : "north_east", - "tags" : [ "arrow", "east", "maps", "navigation", "noth", "right", "up" ] -}, { - "name" : "north", - "tags" : [ "arrow", "directional", "maps", "navigation", "north", "up" ] -}, { - "name" : "cottage", - "tags" : [ "architecture", "beach", "cottage", "estate", "home", "house", "lake", "lodge", "maps", "place", "real", "residence", "residential", "stay", "traveling" ] -}, { - "name" : "local_activity", - "tags" : [ "activity", "event", "event ticket", "local", "star", "things", "ticket" ] -}, { - "name" : "currency_exchange", - "tags" : [ "360", "around", "arrow", "arrows", "cash", "coin", "commerce", "currency", "direction", "dollars", "exchange", "inprogress", "money", "pay", "renew", "rotate", "sync", "turn", "universal" ] -}, { - "name" : "video_library", - "tags" : [ "arrow", "collection", "library", "play", "video" ] -}, { - "name" : "hourglass_bottom", - "tags" : [ "bottom", "countdown", "half", "hourglass", "loading", "minute", "minutes", "time", "wait", "waiting" ] -}, { - "name" : "headphones", - "tags" : [ "accessory", "audio", "device", "ear", "earphone", "headphones", "headset", "listen", "music", "sound" ] -}, { - "name" : "zoom_out", - "tags" : [ "find", "glass", "look", "magnify", "magnifying", "minus", "negative", "out", "scale", "search", "see", "size", "small", "smaller", "zoom" ] -}, { - "name" : "poll", - "tags" : [ "analytics", "bar", "bars", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "poll", "statistics", "survey", "tracking", "vote" ] -}, { - "name" : "perm_contact_calendar", - "tags" : [ "account", "calendar", "contact", "date", "face", "human", "information", "people", "perm", "person", "profile", "schedule", "time", "user" ] -}, { - "name" : "forward", - "tags" : [ "arrow", "forward", "mail", "message", "playback", "right", "sent" ] -}, { - "name" : "person_pin", - "tags" : [ "account", "avatar", "destination", "direction", "face", "human", "location", "maps", "people", "person", "pin", "place", "profile", "stop", "user" ] -}, { - "name" : "home_work", - "tags" : [ "architecture", "building", "estate", "home", "place", "real", "residence", "residential", "shelter", "work" ] -}, { - "name" : "playlist_add_check", - "tags" : [ "add", "approve", "check", "collection", "complete", "done", "list", "mark", "music", "ok", "playlist", "select", "tick", "validate", "verified", "yes" ] -}, { - "name" : "local_cafe", - "tags" : [ "bottle", "cafe", "coffee", "cup", "drink", "food", "restaurant", "tea" ] -}, { - "name" : "ondemand_video", - "tags" : [ "Android", "OS", "chrome", "demand", "desktop", "device", "hardware", "iOS", "mac", "monitor", "ondemand", "play", "television", "tv", "video", "web", "window" ] -}, { - "name" : "design_services", - "tags" : [ "compose", "create", "design", "draft", "edit", "editing", "input", "pen", "pencil", "ruler", "service", "write", "writing" ] -}, { - "name" : "looks_one", - "tags" : [ "1", "digit", "looks", "numbers", "square", "symbol" ] -}, { - "name" : "backup", - "tags" : [ "arrow", "backup", "cloud", "data", "drive", "files folders", "storage", "up", "upload" ] -}, { - "name" : "newspaper", - "tags" : [ "article", "data", "doc", "document", "drive", "file", "folder", "folders", "magazine", "media", "news", "newspaper", "notes", "page", "paper", "sheet", "slide", "text", "writing" ] -}, { - "name" : "memory", - "tags" : [ "card", "chip", "digital", "memory", "micro", "processor", "sd", "storage" ] -}, { - "name" : "open_with", - "tags" : [ "arrow", "arrows", "direction", "expand", "move", "open", "pan", "with" ] -}, { - "name" : "content_cut", - "tags" : [ "content", "copy", "cut", "doc", "document", "file", "past", "scissors", "trim" ] -}, { - "name" : "keyboard", - "tags" : [ "computer", "device", "hardware", "input", "keyboard", "keypad", "letter", "office", "text", "type" ] -}, { - "name" : "hourglass_top", - "tags" : [ "countdown", "half", "hourglass", "loading", "minute", "minutes", "time", "top", "wait", "waiting" ] -}, { - "name" : "settings_phone", - "tags" : [ "call", "cell", "contact", "device", "hardware", "mobile", "phone", "settings", "telephone" ] -}, { - "name" : "rss_feed", - "tags" : [ "application", "blog", "connection", "data", "feed", "internet", "network", "rss", "service", "signal", "website", "wifi", "wireless" ] -}, { - "name" : "first_page", - "tags" : [ "arrow", "back", "chevron", "first", "left", "page", "rewind" ] -}, { - "name" : "delivery_dining", - "tags" : [ "delivery", "dining", "food", "meal", "restaurant", "scooter", "takeout", "transportation", "vehicle", "vespa" ] -}, { - "name" : "rate_review", - "tags" : [ "comment", "feedback", "pen", "pencil", "rate", "review", "stars", "write" ] -}, { - "name" : "control_point", - "tags" : [ "+", "add", "circle", "control", "plus", "point" ] -}, { - "name" : "gpp_good", - "tags" : [ "certified", "check", "good", "gpp", "ok", "pass", "security", "shield", "sim", "tick" ] -}, { - "name" : "circle_notifications", - "tags" : [ "active", "alarm", "alert", "bell", "chime", "circle", "notifications", "notify", "reminder", "ring", "sound" ] -}, { - "name" : "auto_fix_high", - "tags" : [ "adjust", "ai", "artificial", "auto", "automatic", "automation", "custom", "edit", "editing", "enhance", "erase", "fix", "genai", "high", "intelligence", "magic", "modify", "pen", "smart", "spark", "sparkle", "star", "tool", "wand" ] -}, { - "name" : "book_online", - "tags" : [ "Android", "OS", "admission", "appointment", "book", "cell", "device", "event", "hardware", "iOS", "mobile", "online", "pass", "phone", "reservation", "tablet", "ticket" ] -}, { - "name" : "notes", - "tags" : [ "comment", "doc", "document", "note", "notes", "text", "write", "writing" ] -}, { - "name" : "point_of_sale", - "tags" : [ "checkout", "cost", "machine", "merchant", "money", "of", "pay", "payment", "point", "pos", "retail", "sale", "system", "transaction" ] -}, { - "name" : "perm_phone_msg", - "tags" : [ "bubble", "call", "cell", "chat", "comment", "communicate", "contact", "device", "message", "msg", "perm", "phone", "recording", "speech", "telephone", "voice" ] -}, { - "name" : "speaker_notes", - "tags" : [ "bubble", "chat", "comment", "communicate", "format", "list", "message", "notes", "speaker", "speech", "text" ] -}, { - "name" : "fullscreen_exit", - "tags" : [ "adjust", "app", "application", "components", "exit", "full", "fullscreen", "interface", "screen", "site", "size", "ui", "ux", "view", "web", "website" ] -}, { - "name" : "headset_mic", - "tags" : [ "accessory", "audio", "chat", "device", "ear", "earphone", "headphones", "headset", "listen", "mic", "music", "sound", "talk" ] -}, { - "name" : "create_new_folder", - "tags" : [ "+", "add", "create", "data", "doc", "document", "drive", "file", "folder", "new", "plus", "sheet", "slide", "storage", "symbol" ] -}, { - "name" : "wysiwyg", - "tags" : [ "composer", "mode", "screen", "site", "software", "system", "text", "view", "visibility", "web", "website", "window", "wysiwyg" ] -}, { - "name" : "label_important", - "tags" : [ "favorite", "important", "indent", "label", "library", "mail", "remember", "save", "stamp", "sticker", "tag", "wing" ] -}, { - "name" : "card_membership", - "tags" : [ "bill", "bookmark", "card", "cash", "certificate", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "loyalty", "membership", "money", "online", "pay", "payment", "shopping", "subscription" ] -}, { - "name" : "style", - "tags" : [ "booklet", "cards", "filters", "options", "style", "tags" ] -}, { - "name" : "arrow_circle_down", - "tags" : [ "arrow", "circle", "direction", "down", "navigation" ] -}, { - "name" : "file_present", - "tags" : [ "clip", "data", "doc", "document", "drive", "file", "folder", "folders", "note", "paper", "present", "reminder", "sheet", "slide", "storage", "writing" ] -}, { - "name" : "directions_bus", - "tags" : [ "automobile", "bus", "car", "cars", "directions", "maps", "public", "transportation", "vehicle" ] -}, { - "name" : "whatshot", - "tags" : [ "arrow", "circle", "direction", "fire", "frames", "hot", "round", "whatshot" ] -}, { - "name" : "sports_soccer", - "tags" : [ "athlete", "athletic", "ball", "entertainment", "exercise", "football", "game", "hobby", "soccer", "social", "sports" ] -}, { - "name" : "indeterminate_check_box", - "tags" : [ "app", "application", "box", "button", "check", "components", "control", "design", "form", "indeterminate", "interface", "screen", "select", "selected", "selection", "site", "square", "toggle", "ui", "undetermined", "ux", "web", "website" ] -}, { - "name" : "outlined_flag", - "tags" : [ "country", "flag", "goal", "mark", "nation", "outlined", "report", "start" ] -}, { - "name" : "price_change", - "tags" : [ "arrows", "bill", "card", "cash", "change", "coin", "commerce", "cost", "credit", "currency", "dollars", "down", "finance", "money", "online", "pay", "payment", "price", "shopping", "symbol", "up" ] -}, { - "name" : "mark_email_read", - "tags" : [ "approve", "check", "complete", "done", "email", "envelop", "letter", "mail", "mark", "message", "note", "ok", "read", "select", "send", "sent", "tick", "yes" ] -}, { - "name" : "library_add", - "tags" : [ "+", "add", "collection", "layers", "library", "multiple", "music", "new", "plus", "stacked", "symbol", "video" ] -}, { - "name" : "pageview", - "tags" : [ "doc", "document", "find", "glass", "magnifying", "page", "paper", "search", "view" ] -}, { - "name" : "tv", - "tags" : [ "device", "display", "monitor", "screen", "screencast", "stream", "television", "tv", "video", "wireless" ] -}, { - "name" : "inbox", - "tags" : [ "archive", "email", "inbox", "incoming", "mail", "message" ] -}, { - "name" : "adjust", - "tags" : [ "adjust", "alter", "center", "circle", "circles", "dot", "fix", "image", "move", "target" ] -}, { - "name" : "3d_rotation", - "tags" : [ "3", "3d", "D", "alphabet", "arrow", "arrows", "av", "camera", "character", "digit", "font", "letter", "number", "rotation", "symbol", "text", "type", "vr" ] -}, { - "name" : "battery_charging_full", - "tags" : [ "battery", "bolt", "cell", "charge", "charging", "full", "lightening", "mobile", "power", "thunderbolt" ] -}, { - "name" : "chair", - "tags" : [ "chair", "comfort", "couch", "decoration", "furniture", "home", "house", "living", "lounging", "loveseat", "room", "seat", "seating", "sofa" ] -}, { - "name" : "directions_bike", - "tags" : [ "bicycle", "bike", "direction", "directions", "human", "maps", "person", "public", "route", "transportation" ] -}, { - "name" : "mic_off", - "tags" : [ "audio", "disabled", "enabled", "hear", "hearing", "mic", "microphone", "noise", "off", "on", "record", "recording", "slash", "sound", "voice" ] -}, { - "name" : "local_police", - "tags" : [ "911", "badge", "law", "local", "officer", "police", "protect", "protection", "security", "shield" ] -}, { - "name" : "fastfood", - "tags" : [ "drink", "fastfood", "food", "hamburger", "maps", "meal", "places" ] -}, { - "name" : "tungsten", - "tags" : [ "electricity", "indoor", "lamp", "light", "lightbulb", "setting", "tungsten" ] -}, { - "name" : "mood", - "tags" : [ "emoji", "emotions", "expressions", "face", "feelings", "glad", "happiness", "happy", "like", "mood", "person", "pleased", "smile", "smiling", "social", "survey" ] -}, { - "name" : "pause_circle", - "tags" : [ "circle", "control", "controls", "media", "music", "pause", "video" ] -}, { - "name" : "upgrade", - "tags" : [ "arrow", "export", "instal", "line", "replace", "up", "update", "upgrade" ] -}, { - "name" : "recommend", - "tags" : [ "approved", "circle", "confirm", "favorite", "gesture", "hand", "like", "reaction", "recommend", "social", "support", "thumbs", "up", "well" ] -}, { - "name" : "directions_car_filled", - "tags" : [ "automobile", "car", "cars", "direction", "directions", "filled", "maps", "public", "transportation", "vehicle" ] -}, { - "name" : "fmd_good", - "tags" : [ "destination", "direction", "fmd", "good", "location", "maps", "pin", "place", "stop" ] -}, { - "name" : "integration_instructions", - "tags" : [ "brackets", "clipboard", "code", "css", "develop", "developer", "doc", "document", "engineer", "engineering clipboard", "html", "instructions", "integration", "platform" ] -}, { - "name" : "format_bold", - "tags" : [ "B", "alphabet", "bold", "character", "doc", "edit", "editing", "editor", "font", "format", "letter", "sheet", "spreadsheet", "styles", "symbol", "text", "type", "writing" ] -}, { - "name" : "people_outline", - "tags" : [ "accounts", "committee", "face", "family", "friends", "humans", "network", "outline", "people", "persons", "profiles", "social", "team", "users" ] -}, { - "name" : "trending_down", - "tags" : [ "analytics", "arrow", "data", "diagram", "down", "graph", "infographic", "measure", "metrics", "movement", "rate", "rating", "statistics", "tracking", "trending" ] -}, { - "name" : "change_history", - "tags" : [ "change", "history", "shape", "triangle" ] -}, { - "name" : "female", - "tags" : [ "female", "gender", "girl", "lady", "social", "symbol", "woman", "women" ] -}, { - "name" : "link_off", - "tags" : [ "attached", "chain", "clip", "connection", "disabled", "enabled", "link", "linked", "links", "multimedia", "off", "on", "slash", "url" ] -}, { - "name" : "text_fields", - "tags" : [ "T", "add", "alphabet", "character", "field", "fields", "font", "input", "letter", "symbol", "text", "type" ] -}, { - "name" : "swipe", - "tags" : [ "arrow", "arrows", "fingers", "gesture", "hand", "hands", "swipe", "touch" ] -}, { - "name" : "reviews", - "tags" : [ "bubble", "chat", "comment", "communicate", "feedback", "message", "rate", "rating", "recommendation", "reviews", "speech" ] -}, { - "name" : "home_repair_service", - "tags" : [ "box", "equipment", "fix", "home", "kit", "mechanic", "repair", "repairing", "service", "tool", "toolbox", "tools", "workshop" ] -}, { - "name" : "subscriptions", - "tags" : [ "enroll", "list", "media", "order", "play", "signup", "subscribe", "subscriptions" ] -}, { - "name" : "video_call", - "tags" : [ "+", "add", "call", "camera", "chat", "conference", "film", "filming", "hardware", "image", "motion", "new", "picture", "plus", "symbol", "video", "videography" ] -}, { - "name" : "zoom_out_map", - "tags" : [ "arrow", "arrows", "destination", "location", "maps", "move", "out", "place", "stop", "zoom" ] -}, { - "name" : "straighten", - "tags" : [ "length", "measure", "measurement", "ruler", "size", "straighten" ] -}, { - "name" : "arrow_drop_down_circle", - "tags" : [ "app", "application", "arrow", "circle", "components", "direction", "down", "drop", "interface", "navigation", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "bed", - "tags" : [ "bed", "bedroom", "double", "full", "furniture", "home", "hotel", "house", "king", "night", "pillows", "queen", "rest", "room", "size", "sleep" ] -}, { - "name" : "drive_eta", - "tags" : [ "automobile", "car", "cars", "destination", "direction", "drive", "estimate", "eta", "maps", "public", "transportation", "travel", "trip", "vehicle" ] -}, { - "name" : "class", - "tags" : [ "archive", "book", "bookmark", "class", "favorite", "label", "library", "read", "reading", "remember", "ribbon", "save", "tag" ] -}, { - "name" : "drafts", - "tags" : [ "document", "draft", "drafts", "email", "file", "letter", "mail", "message", "read" ] -}, { - "name" : "ballot", - "tags" : [ "ballot", "bullet", "election", "list", "point", "poll", "vote" ] -}, { - "name" : "volume_mute", - "tags" : [ "audio", "control", "music", "mute", "sound", "speaker", "tv", "volume" ] -}, { - "name" : "table_rows", - "tags" : [ "grid", "layout", "lines", "rows", "stacked", "table" ] -}, { - "name" : "accessible", - "tags" : [ "accessibility", "accessible", "body", "handicap", "help", "human", "people", "person", "wheelchair" ] -}, { - "name" : "stop_circle", - "tags" : [ "circle", "control", "controls", "music", "pause", "play", "square", "stop", "video" ] -}, { - "name" : "family_restroom", - "tags" : [ "bathroom", "child", "children", "family", "father", "kids", "mother", "parents", "restroom", "wc" ] -}, { - "name" : "title", - "tags" : [ "T", "alphabet", "character", "font", "header", "letter", "subject", "symbol", "text", "title", "type" ] -}, { - "name" : "biotech", - "tags" : [ "biotech", "chemistry", "laboratory", "microscope", "research", "science", "technology" ] -}, { - "name" : "insert_emoticon", - "tags" : [ "account", "emoji", "emoticon", "face", "happy", "human", "insert", "people", "person", "profile", "sentiment", "smile", "user" ] -}, { - "name" : "g_translate", - "tags" : [ "emblem", "g", "google", "language", "logo", "mark", "speaking", "speech", "translate", "translator", "words" ] -}, { - "name" : "last_page", - "tags" : [ "app", "application", "arrow", "chevron", "components", "end", "forward", "interface", "last", "page", "right", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "publish", - "tags" : [ "arrow", "cloud", "file", "import", "publish", "up", "upload" ] -}, { - "name" : "repeat", - "tags" : [ "arrow", "arrows", "control", "controls", "media", "music", "repeat", "video" ] -}, { - "name" : "checklist_rtl", - "tags" : [ "align", "alignment", "approve", "check", "checklist", "complete", "doc", "done", "edit", "editing", "editor", "format", "list", "mark", "notes", "ok", "rtl", "select", "sheet", "spreadsheet", "text", "tick", "type", "validate", "verified", "writing", "yes" ] -}, { - "name" : "wifi_off", - "tags" : [ "connection", "data", "disabled", "enabled", "internet", "network", "off", "offline", "on", "scan", "service", "signal", "slash", "wifi", "wireless" ] -}, { - "name" : "settings_accessibility", - "tags" : [ "accessibility", "body", "details", "human", "information", "people", "person", "personal", "preferences", "profile", "settings", "user" ] -}, { - "name" : "percent", - "tags" : [ "math", "number", "percent", "symbol" ] -}, { - "name" : "insert_photo", - "tags" : [ "image", "insert", "landscape", "mountain", "mountains", "photo", "photography", "picture" ] -}, { - "name" : "hotel", - "tags" : [ "body", "hotel", "human", "people", "person", "sleep", "stay", "travel", "trip" ] -}, { - "name" : "cleaning_services", - "tags" : [ "clean", "cleaning", "dust", "services", "sweep" ] -}, { - "name" : "downloading", - "tags" : [ "arrow", "circle", "down", "download", "downloading", "downloads", "install", "pending", "progress", "upload" ] -}, { - "name" : "expand", - "tags" : [ "arrow", "arrows", "compress", "enlarge", "expand", "grow", "move", "push", "together" ] -}, { - "name" : "local_phone", - "tags" : [ "booth", "call", "communication", "phone", "telecommunication" ] -}, { - "name" : "offline_bolt", - "tags" : [ "bolt", "circle", "electric", "fast", "lightning", "offline", "thunderbolt" ] -}, { - "name" : "auto_graph", - "tags" : [ "analytics", "auto", "chart", "data", "diagram", "graph", "infographic", "line", "measure", "metrics", "stars", "statistics", "tracking" ] -}, { - "name" : "local_grocery_store", - "tags" : [ "grocery", "market", "shop", "store" ] -}, { - "name" : "photo_library", - "tags" : [ "album", "image", "library", "mountain", "mountains", "photo", "photography", "picture" ] -}, { - "name" : "miscellaneous_services", - "tags" : [ ] -}, { - "name" : "note_alt", - "tags" : [ "alt", "clipboard", "document", "file", "memo", "note", "page", "paper", "writing" ] -}, { - "name" : "settings_backup_restore", - "tags" : [ "arrow", "back", "backup", "backwards", "refresh", "restore", "reverse", "rotate", "settings" ] -}, { - "name" : "production_quantity_limits", - "tags" : [ "!", "alert", "attention", "bill", "card", "cart", "cash", "caution", "coin", "commerce", "credit", "currency", "danger", "dollars", "error", "exclamation", "important", "limits", "mark", "money", "notification", "online", "pay", "payment", "production", "quantity", "shopping", "symbol", "warning" ] -}, { - "name" : "person_off", - "tags" : [ "account", "avatar", "disabled", "enabled", "face", "human", "off", "on", "people", "person", "profile", "slash", "user" ] -}, { - "name" : "report_gmailerrorred", - "tags" : [ "!", "alert", "attention", "caution", "danger", "error", "exclamation", "gmail", "gmailerrorred", "important", "mark", "notification", "octagon", "report", "symbol", "warning" ] -}, { - "name" : "camera", - "tags" : [ "aperture", "camera", "lens", "photo", "photography", "picture", "shutter" ] -}, { - "name" : "recycling", - "tags" : [ "bio", "eco", "green", "loop", "recyclable", "recycle", "recycling", "rotate", "sustainability", "sustainable", "trash" ] -}, { - "name" : "male", - "tags" : [ "boy", "gender", "male", "man", "social", "symbol" ] -}, { - "name" : "not_interested", - "tags" : [ "cancel", "close", "dislike", "exit", "interested", "no", "not", "off", "quit", "remove", "stop", "x" ] -}, { - "name" : "event_busy", - "tags" : [ "busy", "calendar", "cancel", "close", "date", "event", "exit", "no", "remove", "schedule", "stop", "time", "unavailable", "x" ] -}, { - "name" : "arrow_circle_left", - "tags" : [ "arrow", "circle", "direction", "left", "navigation" ] -}, { - "name" : "shuffle", - "tags" : [ "arrow", "arrows", "control", "controls", "music", "random", "shuffle", "video" ] -}, { - "name" : "aspect_ratio", - "tags" : [ "aspect", "expand", "image", "ratio", "resize", "scale", "size", "square" ] -}, { - "name" : "other_houses", - "tags" : [ "architecture", "cottage", "estate", "home", "house", "houses", "maps", "other", "place", "real", "residence", "residential", "stay", "traveling" ] -}, { - "name" : "model_training", - "tags" : [ "arrow", "bulb", "idea", "inprogress", "light", "load", "loading", "model", "refresh", "renew", "restore", "reverse", "rotate", "training" ] -}, { - "name" : "unfold_less", - "tags" : [ "arrow", "arrows", "chevron", "collapse", "direction", "expand", "expandable", "inward", "less", "list", "navigation", "unfold", "up" ] -}, { - "name" : "insert_chart_outlined", - "tags" : [ "analytics", "bar", "bars", "chart", "data", "diagram", "graph", "infographic", "insert", "measure", "metrics", "outlined", "statistics", "tracking" ] -}, { - "name" : "donut_large", - "tags" : [ "analytics", "chart", "data", "diagram", "donut", "graph", "infographic", "inprogress", "large", "measure", "metrics", "pie", "statistics", "tracking" ] -}, { - "name" : "view_column", - "tags" : [ "column", "design", "format", "grid", "layout", "vertical", "view", "website" ] -}, { - "name" : "segment", - "tags" : [ "alignment", "fonts", "format", "lines", "list", "paragraph", "part", "piece", "rule", "rules", "segment", "style", "text" ] -}, { - "name" : "checkroom", - "tags" : [ "checkroom", "closet", "clothes", "coat check", "hanger" ] -}, { - "name" : "mode", - "tags" : [ "compose", "create", "draft", "draw", "edit", "mode", "pen", "pencil", "write" ] -}, { - "name" : "portrait", - "tags" : [ "account", "face", "human", "people", "person", "photo", "picture", "portrait", "profile", "user" ] -}, { - "name" : "camera_alt", - "tags" : [ "alt", "camera", "image", "photo", "photography", "picture" ] -}, { - "name" : "keyboard_double_arrow_left", - "tags" : [ "arrow", "arrows", "direction", "double", "left", "multiple", "navigation" ] -}, { - "name" : "delete_sweep", - "tags" : [ "bin", "can", "delete", "garbage", "remove", "sweep", "trash" ] -}, { - "name" : "hub", - "tags" : [ "center", "connection", "core", "focal point", "hub", "network", "nucleus", "topology" ] -}, { - "name" : "audiotrack", - "tags" : [ "audio", "audiotrack", "key", "music", "note", "sound", "track" ] -}, { - "name" : "calendar_view_month", - "tags" : [ "calendar", "date", "day", "event", "format", "grid", "layout", "month", "schedule", "today", "view" ] -}, { - "name" : "draw", - "tags" : [ "compose", "create", "design", "draft", "draw", "edit", "editing", "input", "pen", "pencil", "write", "writing" ] -}, { - "name" : "navigation", - "tags" : [ "destination", "direction", "location", "maps", "navigation", "pin", "place", "point", "stop" ] -}, { - "name" : "folder_shared", - "tags" : [ "account", "collaboration", "data", "doc", "document", "drive", "face", "file", "folder", "human", "people", "person", "profile", "share", "shared", "sheet", "slide", "storage", "team", "user" ] -}, { - "name" : "read_more", - "tags" : [ "arrow", "more", "read", "text" ] -}, { - "name" : "stacked_bar_chart", - "tags" : [ "analytics", "bar", "chart-chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "stacked", "statistics", "tracking" ] -}, { - "name" : "mode_comment", - "tags" : [ "bubble", "chat", "comment", "communicate", "feedback", "message", "mode comment", "speech" ] -}, { - "name" : "schedule_send", - "tags" : [ "calendar", "clock", "date", "email", "letter", "mail", "remember", "schedule", "send", "share", "time" ] -}, { - "name" : "bluetooth", - "tags" : [ "bluetooth", "cast", "connect", "connection", "device", "paring", "streaming", "symbol", "wireless" ] -}, { - "name" : "graphic_eq", - "tags" : [ "audio", "eq", "equalizer", "graphic", "music", "recording", "sound", "voice" ] -}, { - "name" : "markunread", - "tags" : [ "email", "envelop", "letter", "mail", "markunread", "message", "send", "unread" ] -}, { - "name" : "alarm_on", - "tags" : [ "alarm", "alert", "bell", "clock", "disabled", "duration", "enabled", "notification", "off", "on", "slash", "time", "timer", "watch" ] -}, { - "name" : "local_gas_station", - "tags" : [ "auto", "car", "gas", "local", "oil", "station", "vehicle" ] -}, { - "name" : "person_add_alt_1", - "tags" : [ ] -}, { - "name" : "maximize", - "tags" : [ "app", "application", "components", "design", "interface", "line", "maximize", "screen", "shape", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "bookmark_add", - "tags" : [ "+", "add", "bookmark", "favorite", "plus", "remember", "ribbon", "save", "symbol" ] -}, { - "name" : "dvr", - "tags" : [ "Android", "OS", "audio", "chrome", "computer", "desktop", "device", "display", "dvr", "electronic", "hardware", "iOS", "list", "mac", "monitor", "record", "recorder", "screen", "tv", "video", "web", "window" ] -}, { - "name" : "do_not_disturb_on", - "tags" : [ "cancel", "close", "denied", "deny", "disabled", "disturb", "do", "enabled", "off", "on", "remove", "silence", "slash", "stop" ] -}, { - "name" : "train", - "tags" : [ "automobile", "car", "cars", "direction", "maps", "public", "rail", "subway", "train", "transportation", "vehicle" ] -}, { - "name" : "person_pin_circle", - "tags" : [ "account", "circle", "destination", "direction", "face", "human", "location", "maps", "people", "person", "pin", "place", "profile", "stop", "user" ] -}, { - "name" : "square_foot", - "tags" : [ "construction", "feet", "foot", "inches", "length", "measurement", "ruler", "school", "set", "square", "tools" ] -}, { - "name" : "more_time", - "tags" : [ "+", "add", "clock", "date", "more", "new", "plus", "schedule", "symbol", "time" ] -}, { - "name" : "document_scanner", - "tags" : [ "article", "data", "doc", "document", "drive", "file", "folder", "folders", "notes", "page", "paper", "scan", "scanner", "sheet", "slide", "text", "writing" ] -}, { - "name" : "thumbs_up_down", - "tags" : [ "dislike", "down", "favorite", "fingers", "gesture", "hands", "like", "rate", "rating", "thumbs", "up" ] -}, { - "name" : "settings_ethernet", - "tags" : [ "arrows", "computer", "connect", "connection", "connectivity", "dots", "ethernet", "internet", "network", "settings", "wifi" ] -}, { - "name" : "sort_by_alpha", - "tags" : [ "alphabet", "alphabetize", "az", "by alpha", "character", "font", "letter", "list", "order", "organize", "sort", "symbol", "text", "type" ] -}, { - "name" : "theaters", - "tags" : [ "film", "movie", "movies", "show", "showtimes", "theater", "theaters", "watch" ] -}, { - "name" : "cloud_done", - "tags" : [ "app", "application", "approve", "backup", "check", "cloud", "complete", "connection", "done", "drive", "files", "folders", "internet", "mark", "network", "ok", "select", "sky", "storage", "tick", "upload", "validate", "verified", "yes" ] -}, { - "name" : "local_parking", - "tags" : [ "alphabet", "auto", "car", "character", "font", "garage", "letter", "local", "park", "parking", "symbol", "text", "type", "vehicle" ] -}, { - "name" : "view_agenda", - "tags" : [ "agenda", "cards", "design", "format", "grid", "layout", "stacked", "view", "website" ] -}, { - "name" : "mark_email_unread", - "tags" : [ "check", "circle", "email", "envelop", "letter", "mail", "mark", "message", "note", "notification", "send", "unread" ] -}, { - "name" : "local_florist", - "tags" : [ "florist", "flower", "local", "shop" ] -}, { - "name" : "connect_without_contact", - "tags" : [ "communicating", "connect", "contact", "distance", "people", "signal", "social", "socialize", "without" ] -}, { - "name" : "thumb_down_off_alt", - "tags" : [ "disabled", "dislike", "down", "enabled", "favorite", "filled", "fingers", "gesture", "hand", "hands", "like", "off", "offline", "on", "rank", "ranking", "rate", "rating", "slash", "thumb" ] -}, { - "name" : "sentiment_neutral", - "tags" : [ "emotionless", "emotions", "expressions", "face", "feelings", "fine", "indifference", "mood", "neutral", "okay", "person", "sentiment", "survey" ] -}, { - "name" : "call_end", - "tags" : [ "call", "cell", "contact", "device", "end", "hardware", "mobile", "phone", "telephone" ] -}, { - "name" : "subdirectory_arrow_right", - "tags" : [ "arrow", "directory", "down", "navigation", "right", "sub", "subdirectory" ] -}, { - "name" : "diamond", - "tags" : [ "diamond", "fashion", "gems", "jewelry", "logo", "retail", "valuable", "valuables" ] -}, { - "name" : "podcasts", - "tags" : [ "broadcast", "casting", "network", "podcasts", "signal", "transmitting", "wireless" ] -}, { - "name" : "monitor_heart", - "tags" : [ "baseline", "device", "ecc", "ecg", "fitness", "health", "heart", "medical", "monitor", "track" ] -}, { - "name" : "all_inclusive", - "tags" : [ "all", "endless", "forever", "inclusive", "infinity", "loop", "mobius", "neverending", "strip", "sustainability", "sustainable" ] -}, { - "name" : "wc", - "tags" : [ "bathroom", "closet", "female", "male", "man", "restroom", "room", "wash", "water", "wc", "women" ] -}, { - "name" : "grass", - "tags" : [ "backyard", "fodder", "grass", "ground", "home", "lawn", "plant", "turf", "yard" ] -}, { - "name" : "important_devices", - "tags" : [ "Android", "OS", "desktop", "devices", "hardware", "iOS", "important", "mobile", "monitor", "phone", "star", "tablet", "web" ] -}, { - "name" : "back_hand", - "tags" : [ "back", "fingers", "gesture", "hand", "raised" ] -}, { - "name" : "hiking", - "tags" : [ "backpacking", "bag", "climbing", "duffle", "hiking", "mountain", "social", "sports", "stick", "trail", "travel", "walking" ] -}, { - "name" : "masks", - "tags" : [ "air", "cover", "covid", "face", "hospital", "masks", "medical", "pollution", "protection", "respirator", "sick", "social" ] -}, { - "name" : "waving_hand", - "tags" : [ "bye", "fingers", "gesture", "goodbye", "greetings", "hand", "hello", "palm", "wave", "waving" ] -}, { - "name" : "architecture", - "tags" : [ "architecture", "art", "compass", "design", "draw", "drawing", "engineering", "geometric", "tool" ] -}, { - "name" : "local_post_office", - "tags" : [ "delivery", "email", "envelop", "letter", "local", "mail", "message", "office", "package", "parcel", "post", "postal", "send", "stamp" ] -}, { - "name" : "functions", - "tags" : [ "average", "calculate", "count", "custom", "doc", "edit", "editing", "editor", "functions", "math", "sheet", "spreadsheet", "style", "sum", "text", "type", "writing" ] -}, { - "name" : "directions", - "tags" : [ "arrow", "directions", "maps", "right", "route", "sign", "traffic" ] -}, { - "name" : "money", - "tags" : [ "100", "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "digit", "dollars", "finance", "money", "number", "online", "pay", "payment", "price", "shopping", "symbol" ] -}, { - "name" : "unpublished", - "tags" : [ "approve", "check", "circle", "complete", "disabled", "done", "enabled", "mark", "off", "ok", "on", "select", "slash", "tick", "unpublished", "validate", "verified", "yes" ] -}, { - "name" : "notifications_off", - "tags" : [ "active", "alarm", "alert", "bell", "chime", "disabled", "enabled", "notifications", "notify", "off", "offline", "on", "reminder", "ring", "slash", "sound" ] -}, { - "name" : "airport_shuttle", - "tags" : [ "airport", "automobile", "car", "cars", "commercial", "delivery", "direction", "maps", "mini", "public", "shuttle", "transport", "transportation", "travel", "truck", "van", "vehicle" ] -}, { - "name" : "insert_link", - "tags" : [ "add", "attach", "clip", "file", "insert", "link", "mail", "media" ] -}, { - "name" : "thumb_down_alt", - "tags" : [ "bad", "decline", "disapprove", "dislike", "down", "feedback", "hate", "negative", "no", "reject", "social", "thumb", "veto", "vote" ] -}, { - "name" : "two_wheeler", - "tags" : [ "automobile", "bike", "car", "cars", "direction", "maps", "motorcycle", "public", "scooter", "sport", "transportation", "travel", "two wheeler", "vehicle" ] -}, { - "name" : "nightlight", - "tags" : [ "dark", "disturb", "mode", "moon", "night", "nightlight", "sleep" ] -}, { - "name" : "mic_none", - "tags" : [ "hear", "hearing", "mic", "microphone", "noise", "none", "record", "sound", "voice" ] -}, { - "name" : "keyboard_double_arrow_down", - "tags" : [ "arrow", "arrows", "direction", "double", "down", "multiple", "navigation" ] -}, { - "name" : "invert_colors", - "tags" : [ "colors", "drop", "droplet", "edit", "editing", "hue", "invert", "inverted", "palette", "tone", "water" ] -}, { - "name" : "clear_all", - "tags" : [ "all", "clear", "doc", "document", "format", "lines", "list" ] -}, { - "name" : "mouse", - "tags" : [ "click", "computer", "cursor", "device", "hardware", "mouse", "wireless" ] -}, { - "name" : "mode_edit_outline", - "tags" : [ "compose", "create", "draft", "draw", "edit", "mode", "outline", "pen", "pencil", "write" ] -}, { - "name" : "open_in_browser", - "tags" : [ "arrow", "browser", "in", "open", "site", "up", "web", "website", "window" ] -}, { - "name" : "insert_invitation", - "tags" : [ "calendar", "date", "day", "event", "insert", "invitation", "mark", "month", "range", "remember", "reminder", "today", "week" ] -}, { - "name" : "fast_rewind", - "tags" : [ "back", "control", "fast", "media", "music", "play", "rewind", "speed", "time", "tv", "video" ] -}, { - "name" : "opacity", - "tags" : [ "color", "drop", "droplet", "hue", "invert", "inverted", "opacity", "palette", "tone", "water" ] -}, { - "name" : "video_camera_front", - "tags" : [ "account", "camera", "face", "front", "human", "image", "people", "person", "photo", "photography", "picture", "profile", "user", "video" ] -}, { - "name" : "commute", - "tags" : [ "automobile", "car", "commute", "direction", "maps", "public", "train", "transportation", "trip", "vehicle" ] -}, { - "name" : "addchart", - "tags" : [ "+", "addchart", "analytics", "bar", "bars", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "new", "plus", "statistics", "symbol", "tracking" ] -}, { - "name" : "no_accounts", - "tags" : [ "account", "accounts", "avatar", "disabled", "enabled", "face", "human", "no", "off", "offline", "on", "people", "person", "profile", "slash", "thumbnail", "unavailable", "unidentifiable", "unknown", "user" ] -}, { - "name" : "coffee", - "tags" : [ "beverage", "coffee", "cup", "drink", "mug", "plate", "set", "tea" ] -}, { - "name" : "luggage", - "tags" : [ "airport", "bag", "baggage", "carry", "flight", "hotel", "luggage", "on", "suitcase", "travel", "trip" ] -}, { - "name" : "workspaces", - "tags" : [ "circles", "collaboration", "dot", "filled", "group", "outline", "space", "team", "work", "workspaces" ] -}, { - "name" : "child_care", - "tags" : [ "babies", "baby", "care", "child", "children", "face", "infant", "kids", "newborn", "toddler", "young" ] -}, { - "name" : "sports_score", - "tags" : [ "destination", "flag", "goal", "score", "sports" ] -}, { - "name" : "library_music", - "tags" : [ "add", "album", "collection", "library", "music", "song", "sounds" ] -}, { - "name" : "history_toggle_off", - "tags" : [ "clock", "date", "history", "off", "schedule", "time", "toggle" ] -}, { - "name" : "system_update_alt", - "tags" : [ "arrow", "down", "download", "export", "system", "update" ] -}, { - "name" : "access_time", - "tags" : [ ] -}, { - "name" : "rotate_right", - "tags" : [ "around", "arrow", "direction", "inprogress", "load", "loading refresh", "renew", "right", "rotate", "turn" ] -}, { - "name" : "color_lens", - "tags" : [ "art", "color", "lens", "paint", "pallet" ] -}, { - "name" : "grid_on", - "tags" : [ "collage", "disabled", "enabled", "grid", "image", "layout", "off", "on", "slash", "view" ] -}, { - "name" : "crop_free", - "tags" : [ "adjust", "adjustments", "crop", "edit", "editing", "focus", "frame", "free", "image", "photo", "photos", "settings", "size", "zoom" ] -}, { - "name" : "cloud_queue", - "tags" : [ "cloud", "connection", "internet", "network", "queue", "sky", "upload" ] -}, { - "name" : "keyboard_voice", - "tags" : [ "keyboard", "mic", "microphone", "noise", "record", "recorder", "speaker", "voice" ] -}, { - "name" : "format_align_left", - "tags" : [ "align", "alignment", "doc", "edit", "editing", "editor", "format", "left", "sheet", "spreadsheet", "text", "type", "writing" ] -}, { - "name" : "view_week", - "tags" : [ "bars", "columns", "design", "format", "grid", "layout", "view", "website", "week" ] -}, { - "name" : "real_estate_agent", - "tags" : [ "agent", "architecture", "broker", "estate", "hand", "home", "house", "loan", "mortgage", "property", "real", "residence", "residential", "sales", "social" ] -}, { - "name" : "horizontal_rule", - "tags" : [ "gmail", "horizontal", "line", "novitas", "rule" ] -}, { - "name" : "topic", - "tags" : [ "data", "doc", "document", "drive", "file", "folder", "sheet", "slide", "storage", "topic" ] -}, { - "name" : "shower", - "tags" : [ "bath", "bathroom", "closet", "home", "house", "place", "plumbing", "room", "shower", "sprinkler", "wash", "water", "wc" ] -}, { - "name" : "format_italic", - "tags" : [ "alphabet", "character", "doc", "edit", "editing", "editor", "font", "format", "italic", "letter", "sheet", "spreadsheet", "style", "symbol", "text", "type", "writing" ] -}, { - "name" : "traffic", - "tags" : [ "direction", "light", "maps", "signal", "street", "traffic" ] -}, { - "name" : "add_business", - "tags" : [ "+", "add", "bill", "building", "business", "card", "cash", "coin", "commerce", "company", "credit", "currency", "dollars", "market", "money", "new", "online", "pay", "payment", "plus", "shop", "shopping", "store", "storefront", "symbol" ] -}, { - "name" : "electrical_services", - "tags" : [ "charge", "cord", "electric", "electrical", "plug", "power", "services", "wire" ] -}, { - "name" : "timelapse", - "tags" : [ "duration", "motion", "photo", "time", "timelapse", "timer", "video" ] -}, { - "name" : "youtube_searched_for", - "tags" : [ "arrow", "back", "backwards", "find", "glass", "history", "inprogress", "load", "loading", "look", "magnify", "magnifying", "refresh", "renew", "restore", "reverse", "rotate", "search", "see", "youtube" ] -}, { - "name" : "front_hand", - "tags" : [ "fingers", "front", "gesture", "hand", "hello", "palm", "stop" ] -}, { - "name" : "yard", - "tags" : [ "backyard", "flower", "garden", "home", "house", "nature", "pettle", "plants", "yard" ] -}, { - "name" : "tour", - "tags" : [ "destination", "flag", "places", "tour", "travel", "visit" ] -}, { - "name" : "factory", - "tags" : [ "factory", "industry", "manufacturing", "warehouse" ] -}, { - "name" : "developer_board", - "tags" : [ "board", "chip", "computer", "developer", "development", "hardware", "microchip", "processor" ] -}, { - "name" : "more", - "tags" : [ "3", "archive", "bookmark", "dots", "etc", "favorite", "indent", "label", "more", "remember", "save", "stamp", "sticker", "tab", "tag", "three" ] -}, { - "name" : "star_purple500", - "tags" : [ "500", "best", "bookmark", "favorite", "highlight", "purple", "ranking", "rate", "rating", "save", "star", "toggle" ] -}, { - "name" : "format_color_fill", - "tags" : [ "bucket", "color", "doc", "edit", "editing", "editor", "fill", "format", "paint", "sheet", "spreadsheet", "style", "text", "type", "writing" ] -}, { - "name" : "beach_access", - "tags" : [ "access", "beach", "places", "summer", "sunny", "umbrella" ] -}, { - "name" : "local_bar", - "tags" : [ "alcohol", "bar", "bottle", "club", "cocktail", "drink", "food", "liquor", "local", "wine" ] -}, { - "name" : "add_link", - "tags" : [ "add", "attach", "clip", "link", "new", "plus", "symbol" ] -}, { - "name" : "landscape", - "tags" : [ "image", "landscape", "mountain", "mountains", "nature", "photo", "photography", "picture" ] -}, { - "name" : "slideshow", - "tags" : [ "movie", "photos", "play", "slideshow", "square", "video", "view" ] -}, { - "name" : "stream", - "tags" : [ "cast", "connected", "feed", "live", "network", "signal", "stream", "wireless" ] -}, { - "name" : "videocam_off", - "tags" : [ "cam", "camera", "conference", "disabled", "enabled", "film", "filming", "hardware", "image", "motion", "off", "offline", "on", "picture", "slash", "video", "videography" ] -}, { - "name" : "directions_boat", - "tags" : [ "automobile", "boat", "car", "cars", "direction", "directions", "ferry", "maps", "public", "transportation", "vehicle" ] -}, { - "name" : "download_done", - "tags" : [ "arrow", "arrows", "check", "done", "down", "download", "downloads", "drive", "install", "installed", "ok", "tick", "upload" ] -}, { - "name" : "volume_down", - "tags" : [ "audio", "control", "down", "music", "sound", "speaker", "tv", "volume" ] -}, { - "name" : "alt_route", - "tags" : [ "alt", "alternate", "alternative", "arrows", "direction", "maps", "navigation", "options", "other", "route", "routes", "split", "symbol" ] -}, { - "name" : "mood_bad", - "tags" : [ "bad", "disappointment", "dislike", "emoji", "emotions", "expressions", "face", "feelings", "mood", "person", "rating", "social", "survey", "unhappiness", "unhappy", "unpleased", "unsmile", "unsmiling" ] -}, { - "name" : "vaccines", - "tags" : [ "aid", "covid", "doctor", "drug", "emergency", "hospital", "immunity", "injection", "medical", "medication", "medicine", "needle", "pharmacy", "sick", "syringe", "vaccination", "vaccines", "vial" ] -}, { - "name" : "dialpad", - "tags" : [ "buttons", "call", "contact", "device", "dial", "dialpad", "dots", "mobile", "numbers", "pad", "phone" ] -}, { - "name" : "route", - "tags" : [ "directions", "maps", "path", "route", "sign", "traffic" ] -}, { - "name" : "hide_source", - "tags" : [ "circle", "disabled", "enabled", "hide", "off", "offline", "on", "shape", "slash", "source" ] -}, { - "name" : "bookmark_added", - "tags" : [ "added", "approve", "bookmark", "check", "complete", "done", "favorite", "mark", "ok", "remember", "save", "select", "tick", "validate", "verified", "yes" ] -}, { - "name" : "mark_as_unread", - "tags" : [ "as", "envelop", "letter", "mail", "mark", "post", "postal", "read", "receive", "send", "unread" ] -}, { - "name" : "plagiarism", - "tags" : [ "doc", "document", "find", "glass", "look", "magnifying", "page", "paper", "plagiarism", "search", "see" ] -}, { - "name" : "turned_in", - "tags" : [ "archive", "bookmark", "favorite", "in", "label", "library", "read", "reading", "remember", "ribbon", "save", "tag", "turned" ] -}, { - "name" : "settings_input_antenna", - "tags" : [ "airplay", "antenna", "arrows", "cast", "computer", "connect", "connection", "connectivity", "dots", "input", "internet", "network", "screencast", "settings", "stream", "wifi", "wireless" ] -}, { - "name" : "shop", - "tags" : [ "bag", "bill", "buy", "card", "cart", "cash", "coin", "commerce", "credit", "currency", "dollars", "google", "money", "online", "pay", "payment", "play", "shop", "shopping", "store" ] -}, { - "name" : "pool", - "tags" : [ "athlete", "athletic", "beach", "body", "entertainment", "exercise", "hobby", "human", "ocean", "people", "person", "places", "pool", "sea", "sports", "swim", "swimming", "water" ] -}, { - "name" : "search_off", - "tags" : [ "cancel", "close", "disabled", "enabled", "find", "glass", "look", "magnify", "magnifying", "off", "on", "search", "see", "slash", "stop", "x" ] -}, { - "name" : "approval", - "tags" : [ "apply", "approval", "approvals", "approve", "certificate", "certification", "disapproval", "drive", "file", "impression", "ink", "mark", "postage", "stamp" ] -}, { - "name" : "currency_rupee", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "money", "online", "pay", "payment", "price", "rupee", "shopping", "symbol" ] -}, { - "name" : "power", - "tags" : [ "charge", "cord", "electric", "electrical", "outlet", "plug", "power" ] -}, { - "name" : "collections_bookmark", - "tags" : [ "album", "archive", "bookmark", "collections", "favorite", "gallery", "label", "library", "read", "reading", "remember", "ribbon", "save", "stack", "tag" ] -}, { - "name" : "not_started", - "tags" : [ "circle", "media", "not", "pause", "play", "started", "video" ] -}, { - "name" : "pedal_bike", - "tags" : [ "automobile", "bicycle", "bike", "car", "cars", "direction", "human", "maps", "pedal", "public", "route", "scooter", "transportation", "vehicle", "vespa" ] -}, { - "name" : "water", - "tags" : [ "aqua", "beach", "lake", "ocean", "river", "water", "waves", "weather" ] -}, { - "name" : "router", - "tags" : [ "box", "cable", "connection", "hardware", "internet", "network", "router", "signal", "wifi" ] -}, { - "name" : "flight_land", - "tags" : [ "airport", "arrival", "arriving", "flight", "fly", "land", "landing", "plane", "transportation", "travel" ] -}, { - "name" : "shopping_cart_checkout", - "tags" : [ "arrow", "cart", "cash", "checkout", "coin", "commerce", "currency", "dollars", "money", "online", "pay", "payment", "right", "shopping" ] -}, { - "name" : "agriculture", - "tags" : [ "agriculture", "automobile", "car", "cars", "cultivation", "farm", "harvest", "maps", "tractor", "transport", "travel", "truck", "vehicle" ] -}, { - "name" : "where_to_vote", - "tags" : [ "approve", "ballot", "check", "complete", "destination", "direction", "done", "location", "maps", "mark", "ok", "pin", "place", "poll", "select", "stop", "tick", "to", "validate election", "verified", "vote", "where", "yes" ] -}, { - "name" : "beenhere", - "tags" : [ "approve", "archive", "beenhere", "bookmark", "check", "complete", "done", "favorite", "label", "library", "mark", "ok", "read", "reading", "remember", "ribbon", "save", "select", "tag", "tick", "validate", "verified", "yes" ] -}, { - "name" : "add_comment", - "tags" : [ "+", "add", "bubble", "chat", "comment", "communicate", "feedback", "message", "new", "plus", "speech", "symbol" ] -}, { - "name" : "copy_all", - "tags" : [ "all", "content", "copy", "cut", "doc", "document", "file", "multiple", "page", "paper", "past" ] -}, { - "name" : "dynamic_feed", - "tags" : [ "'mail_outline'", "'markunread'. Keep 'mail' and remove others.", "Duplicate of 'email'" ] -}, { - "name" : "videogame_asset", - "tags" : [ "asset", "console", "controller", "device", "game", "gamepad", "gaming", "playstation", "video" ] -}, { - "name" : "move_to_inbox", - "tags" : [ "archive", "arrow", "down", "email", "envelop", "inbox", "incoming", "letter", "mail", "message", "move to", "send" ] -}, { - "name" : "crop_square", - "tags" : [ "adjust", "adjustments", "app", "application", "area", "components", "crop", "design", "edit", "editing", "expand", "frame", "image", "images", "interface", "open", "photo", "photos", "rectangle", "screen", "settings", "shape", "shapes", "site", "size", "square", "ui", "ux", "web", "website", "window" ] -}, { - "name" : "recent_actors", - "tags" : [ "account", "actors", "avatar", "card", "cards", "carousel", "face", "human", "layers", "list", "people", "person", "profile", "recent", "thumbnail", "user" ] -}, { - "name" : "emoji_nature", - "tags" : [ "animal", "bee", "bug", "daisy", "emoji", "flower", "insect", "ladybug", "nature", "petals", "spring", "summer" ] -}, { - "name" : "cloud_off", - "tags" : [ "app", "application", "backup", "cloud", "connection", "disabled", "drive", "enabled", "files", "folders", "internet", "network", "off", "offline", "on", "sky", "slash", "storage", "upload" ] -}, { - "name" : "panorama_fish_eye", - "tags" : [ "angle", "circle", "eye", "fish", "image", "panorama", "photo", "photography", "picture", "wide" ] -}, { - "name" : "lens", - "tags" : [ "circle", "full", "geometry", "lens", "moon" ] -}, { - "name" : "360", - "tags" : [ "360", "arrow", "av", "camera", "direction", "rotate", "rotation", "vr" ] -}, { - "name" : "share_location", - "tags" : [ "destination", "direction", "gps", "location", "maps", "pin", "place", "share", "stop", "tracking" ] -}, { - "name" : "assignment_late", - "tags" : [ "!", "alert", "assignment", "attention", "caution", "clipboard", "danger", "doc", "document", "error", "exclamation", "important", "late", "mark", "notification", "symbol", "warning" ] -}, { - "name" : "switch_account", - "tags" : [ "account", "choices", "face", "human", "multiple", "options", "people", "person", "profile", "social", "switch", "user" ] -}, { - "name" : "looks_two", - "tags" : [ "2", "digit", "looks", "numbers", "square", "symbol" ] -}, { - "name" : "do_not_disturb", - "tags" : [ "cancel", "close", "denied", "deny", "disturb", "do", "remove", "silence", "stop" ] -}, { - "name" : "donut_small", - "tags" : [ "analytics", "chart", "data", "diagram", "donut", "graph", "infographic", "inprogress", "measure", "metrics", "pie", "small", "statistics", "tracking" ] -}, { - "name" : "saved_search", - "tags" : [ "find", "glass", "important", "look", "magnify", "magnifying", "marked", "saved", "search", "see", "star" ] -}, { - "name" : "contactless", - "tags" : [ "bluetooth", "cash", "connect", "connection", "connectivity", "contact", "contactless", "credit", "device", "finance", "pay", "payment", "signal", "transaction", "wifi", "wireless" ] -}, { - "name" : "highlight_alt", - "tags" : [ "alt", "arrow", "box", "click", "cursor", "draw", "focus", "highlight", "pointer", "select", "selection", "target" ] -}, { - "name" : "assignment_return", - "tags" : [ "arrow", "assignment", "back", "clipboard", "doc", "document", "left", "retun" ] -}, { - "name" : "kitchen", - "tags" : [ "appliance", "cold", "food", "fridge", "home", "house", "ice", "kitchen", "places", "refrigerator", "storage" ] -}, { - "name" : "warehouse", - "tags" : [ "garage", "industry", "manufacturing", "storage", "warehouse" ] -}, { - "name" : "liquor", - "tags" : [ "alcohol", "bar", "bottle", "club", "cocktail", "drink", "food", "liquor", "party", "store", "wine" ] -}, { - "name" : "gpp_maybe", - "tags" : [ "!", "alert", "attention", "caution", "certified", "danger", "error", "exclamation", "gpp", "important", "mark", "maybe", "notification", "privacy", "private", "protect", "protection", "security", "shield", "sim", "symbol", "verified", "warning" ] -}, { - "name" : "settings_input_component", - "tags" : [ "audio", "av", "cable", "cables", "component", "connect", "connection", "connectivity", "input", "internet", "plug", "points", "settings", "video", "wifi" ] -}, { - "name" : "waves", - "tags" : [ "beach", "lake", "ocean", "pool", "river", "sea", "swim", "water", "wave", "waves" ] -}, { - "name" : "hotel_class", - "tags" : [ "achievement", "bookmark", "class", "favorite", "highlight", "hotel", "important", "marked", "rank", "ranking", "rate", "rating", "reward", "save", "saved", "shape", "special", "star" ] -}, { - "name" : "web_asset", - "tags" : [ "-website", "app", "application desktop", "asset", "browser", "design", "download", "image", "interface", "internet", "layout", "screen", "site", "ui", "ux", "video", "web", "website", "window", "www" ] -}, { - "name" : "view_carousel", - "tags" : [ "cards", "carousel", "design", "format", "grid", "layout", "view", "website" ] -}, { - "name" : "anchor", - "tags" : [ "anchor", "google", "logo" ] -}, { - "name" : "filter_alt_off", - "tags" : [ "alt", "disabled", "edit", "filter", "funnel", "off", "offline", "options", "refine", "sift", "slash" ] -}, { - "name" : "balance", - "tags" : [ "balance", "equal", "equity", "impartiality", "justice", "parity", "stability. equilibrium", "steadiness", "symmetry" ] -}, { - "name" : "view_quilt", - "tags" : [ "design", "format", "grid", "layout", "quilt", "square", "squares", "stacked", "view", "website" ] -}, { - "name" : "library_add_check", - "tags" : [ "add", "approve", "check", "collection", "complete", "done", "layers", "library", "mark", "multiple", "music", "ok", "select", "stacked", "tick", "validate", "verified", "video", "yes" ] -}, { - "name" : "queue_music", - "tags" : [ "collection", "list", "music", "playlist", "queue" ] -}, { - "name" : "casino", - "tags" : [ "casino", "dice", "dots", "entertainment", "gamble", "gambling", "game", "games", "luck", "places" ] -}, { - "name" : "hearing", - "tags" : [ "accessibility", "accessible", "aid", "ear", "handicap", "hearing", "help", "impaired", "listen", "sound", "volume" ] -}, { - "name" : "phone_enabled", - "tags" : [ "call", "cell", "contact", "device", "enabled", "hardware", "mobile", "phone", "telephone" ] -}, { - "name" : "linear_scale", - "tags" : [ "app", "application", "components", "design", "interface", "layout", "linear", "measure", "menu", "scale", "screen", "site", "slider", "ui", "ux", "web", "website", "window" ] -}, { - "name" : "holiday_village", - "tags" : [ "architecture", "beach", "camping", "cottage", "estate", "holiday", "home", "house", "lake", "lodge", "maps", "place", "real", "residence", "residential", "stay", "traveling", "vacation", "village" ] -}, { - "name" : "turned_in_not", - "tags" : [ "archive", "bookmark", "favorite", "in", "label", "library", "not", "read", "reading", "remember", "ribbon", "save", "tag", "turned" ] -}, { - "name" : "sync_problem", - "tags" : [ "!", "360", "alert", "around", "arrow", "arrows", "attention", "caution", "danger", "direction", "error", "exclamation", "important", "inprogress", "load", "loading refresh", "mark", "notification", "problem", "renew", "rotate", "symbol", "sync", "turn", "warning" ] -}, { - "name" : "start", - "tags" : [ "arrow", "keyboard", "next", "right", "start" ] -}, { - "name" : "all_inbox", - "tags" : [ "Inbox", "all", "delivered", "delivery", "email", "mail", "message", "send" ] -}, { - "name" : "mediation", - "tags" : [ "arrow", "arrows", "direction", "dots", "mediation", "right" ] -}, { - "name" : "edit_off", - "tags" : [ "compose", "create", "disabled", "draft", "edit", "editing", "enabled", "input", "new", "off", "offline", "on", "pen", "pencil", "slash", "write", "writing" ] -}, { - "name" : "emergency", - "tags" : [ "asterisk", "clinic", "emergency", "health", "hospital", "maps", "medical", "symbol" ] -}, { - "name" : "settings_remote", - "tags" : [ "bluetooth", "connection", "connectivity", "device", "remote", "settings", "signal", "wifi", "wireless" ] -}, { - "name" : "drive_file_move", - "tags" : [ "arrow", "data", "doc", "document", "drive", "file", "folder", "move", "right", "sheet", "slide", "storage" ] -}, { - "name" : "fit_screen", - "tags" : [ "enlarge", "fit", "format", "layout", "reduce", "scale", "screen", "size" ] -}, { - "name" : "hourglass_full", - "tags" : [ "countdown", "full", "hourglass", "loading", "minutes", "time", "wait", "waiting" ] -}, { - "name" : "nights_stay", - "tags" : [ "climate", "cloud", "crescent", "dark", "lunar", "mode", "moon", "nights", "phases", "silence", "silent", "sky", "stay", "time", "weather" ] -}, { - "name" : "pause_circle_filled", - "tags" : [ "circle", "control", "controls", "filled", "media", "music", "pause", "video" ] -}, { - "name" : "catching_pokemon", - "tags" : [ "catching", "go", "pokemon", "pokestop", "travel" ] -}, { - "name" : "king_bed", - "tags" : [ "bed", "bedroom", "double", "furniture", "home", "hotel", "house", "king", "night", "pillows", "queen", "rest", "room", "sleep" ] -}, { - "name" : "flaky", - "tags" : [ "approve", "check", "close", "complete", "contrast", "done", "exit", "flaky", "mark", "no", "ok", "options", "select", "stop", "tick", "verified", "x", "yes" ] -}, { - "name" : "format_size", - "tags" : [ "alphabet", "character", "color", "doc", "edit", "editing", "editor", "fill", "font", "format", "letter", "paint", "sheet", "size", "spreadsheet", "style", "symbol", "text", "type", "writing" ] -}, { - "name" : "interests", - "tags" : [ "circle", "heart", "interests", "shapes", "social", "square", "triangle" ] -}, { - "name" : "stacked_line_chart", - "tags" : [ "analytics", "chart", "data", "diagram", "graph", "infographic", "line", "measure", "metrics", "stacked", "statistics", "tracking" ] -}, { - "name" : "unarchive", - "tags" : [ "archive", "arrow", "inbox", "mail", "store", "unarchive", "undo", "up" ] -}, { - "name" : "subtitles", - "tags" : [ "accessible", "caption", "cc", "character", "closed", "decoder", "language", "media", "movies", "subtitle", "subtitles", "tv" ] -}, { - "name" : "toll", - "tags" : [ "bill", "booth", "car", "card", "cash", "coin", "commerce", "credit", "currency", "dollars", "highway", "money", "online", "pay", "payment", "ticket", "toll" ] -}, { - "name" : "keyboard_double_arrow_up", - "tags" : [ "arrow", "arrows", "direction", "double", "multiple", "navigation", "up" ] -}, { - "name" : "time_to_leave", - "tags" : [ "automobile", "car", "cars", "destination", "direction", "drive", "estimate", "eta", "maps", "public", "transportation", "travel", "trip", "vehicle" ] -}, { - "name" : "location_searching", - "tags" : [ "destination", "direction", "location", "maps", "pin", "place", "pointer", "searching", "stop", "tracking" ] -}, { - "name" : "cable", - "tags" : [ "cable", "connect", "connection", "device", "electronics", "usb", "wire" ] -}, { - "name" : "moving", - "tags" : [ "arrow", "direction", "moving", "navigation", "travel", "up" ] -}, { - "name" : "remove_shopping_cart", - "tags" : [ "card", "cart", "cash", "checkout", "coin", "commerce", "credit", "currency", "disabled", "dollars", "enabled", "off", "on", "online", "pay", "payment", "remove", "shopping", "slash", "tick" ] -}, { - "name" : "cast_for_education", - "tags" : [ "Android", "OS", "airplay", "cast", "chrome", "connect", "desktop", "device", "display", "education", "for", "hardware", "iOS", "learning", "lessons teaching", "mac", "monitor", "screen", "screencast", "streaming", "television", "tv", "web", "window", "wireless" ] -}, { - "name" : "fiber_new", - "tags" : [ "alphabet", "character", "fiber", "font", "letter", "network", "new", "symbol", "text", "type" ] -}, { - "name" : "format_underlined", - "tags" : [ "alphabet", "character", "doc", "edit", "editing", "editor", "font", "format", "letter", "line", "sheet", "spreadsheet", "style", "symbol", "text", "type", "under", "underlined", "writing" ] -}, { - "name" : "pause_circle_outline", - "tags" : [ "circle", "control", "controls", "media", "music", "outline", "pause", "video" ] -}, { - "name" : "mark_chat_unread", - "tags" : [ "bubble", "chat", "circle", "comment", "communicate", "mark", "message", "notification", "speech", "unread" ] -}, { - "name" : "insert_comment", - "tags" : [ "add", "bubble", "chat", "comment", "feedback", "insert", "message" ] -}, { - "name" : "cameraswitch", - "tags" : [ "arrows", "camera", "cameraswitch", "flip", "rotate", "swap", "switch", "view" ] -}, { - "name" : "rocket", - "tags" : [ "rocket", "space", "spaceship" ] -}, { - "name" : "local_airport", - "tags" : [ "air", "airplane", "airport", "flight", "plane", "transportation", "travel", "trip" ] -}, { - "name" : "lock_clock", - "tags" : [ "clock", "date", "lock", "locked", "password", "privacy", "private", "protection", "safety", "schedule", "secure", "security", "time" ] -}, { - "name" : "device_hub", - "tags" : [ "Android", "OS", "circle", "computer", "desktop", "device", "hardware", "hub", "iOS", "laptop", "mobile", "monitor", "phone", "square", "tablet", "triangle", "watch", "wearable", "web" ] -}, { - "name" : "filter_vintage", - "tags" : [ "edit", "editing", "effect", "filter", "flower", "image", "images", "photography", "picture", "pictures", "vintage" ] -}, { - "name" : "sailing", - "tags" : [ "boat", "entertainment", "fishing", "hobby", "ocean", "sailboat", "sailing", "sea", "social sports", "travel", "water" ] -}, { - "name" : "roofing", - "tags" : [ "architecture", "building", "chimney", "construction", "estate", "home", "house", "real", "residence", "residential", "roof", "roofing", "service", "shelter" ] -}, { - "name" : "settings_voice", - "tags" : [ "mic", "microphone", "record", "recorder", "settings", "speaker", "voice" ] -}, { - "name" : "swap_horizontal_circle", - "tags" : [ "arrow", "arrows", "back", "circle", "forward", "horizontal", "swap" ] -}, { - "name" : "add_location_alt", - "tags" : [ "+", "add", "alt", "destination", "direction", "location", "maps", "new", "pin", "place", "plus", "stop", "symbol" ] -}, { - "name" : "room_service", - "tags" : [ "alert", "bell", "delivery", "hotel", "notify", "room", "service" ] -}, { - "name" : "content_paste_search", - "tags" : [ "clipboard", "content", "doc", "document", "file", "find", "paste", "search", "trace", "track" ] -}, { - "name" : "reply_all", - "tags" : [ "all", "arrow", "backward", "group", "left", "mail", "message", "multiple", "reply", "send", "share" ] -}, { - "name" : "compost", - "tags" : [ "bio", "compost", "compostable", "decomposable", "decompose", "eco", "green", "leaf", "leafs", "nature", "organic", "plant", "recycle", "sustainability", "sustainable" ] -}, { - "name" : "bubble_chart", - "tags" : [ "analytics", "bar", "bars", "bubble", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "statistics", "tracking" ] -}, { - "name" : "compare", - "tags" : [ "adjust", "adjustment", "compare", "edit", "editing", "edits", "enhance", "fix", "image", "images", "photo", "photography", "photos", "scan", "settings" ] -}, { - "name" : "money_off", - "tags" : [ "bill", "card", "cart", "cash", "coin", "commerce", "credit", "currency", "disabled", "dollars", "enabled", "money", "off", "on", "online", "pay", "payment", "shopping", "slash", "symbol" ] -}, { - "name" : "file_open", - "tags" : [ "arrow", "doc", "document", "drive", "file", "left", "open", "page", "paper" ] -}, { - "name" : "filter_drama", - "tags" : [ "cloud", "drama", "edit", "editing", "effect", "filter", "image", "photo", "photography", "picture", "sky camera" ] -}, { - "name" : "shortcut", - "tags" : [ "arrow", "direction", "forward", "right", "shortcut" ] -}, { - "name" : "view_sidebar", - "tags" : [ "design", "format", "grid", "layout", "sidebar", "view", "web" ] -}, { - "name" : "looks_3", - "tags" : [ "3", "digit", "looks", "numbers", "square", "symbol" ] -}, { - "name" : "note", - "tags" : [ "bookmark", "message", "note", "paper" ] -}, { - "name" : "vertical_align_bottom", - "tags" : [ "align", "alignment", "arrow", "bottom", "doc", "down", "edit", "editing", "editor", "sheet", "spreadsheet", "text", "type", "vertical", "writing" ] -}, { - "name" : "3p", - "tags" : [ "3", "3p", "account", "avatar", "bubble", "chat", "comment", "communicate", "face", "human", "message", "party", "people", "person", "profile", "speech", "user" ] -}, { - "name" : "online_prediction", - "tags" : [ "bulb", "connection", "idea", "light", "network", "online", "prediction", "signal", "wireless" ] -}, { - "name" : "cancel_presentation", - "tags" : [ "cancel", "close", "device", "exit", "no", "present", "presentation", "quit", "remove", "screen", "slide", "stop", "website", "window", "x" ] -}, { - "name" : "select_all", - "tags" : [ "all", "select", "selection", "square", "tool" ] -}, { - "name" : "event_seat", - "tags" : [ "assign", "assigned", "chair", "event", "furniture", "reservation", "row", "seat", "section", "sit" ] -}, { - "name" : "window", - "tags" : [ "close", "glass", "grid", "home", "house", "interior", "layout", "outside", "window" ] -}, { - "name" : "av_timer", - "tags" : [ "av", "clock", "countdown", "duration", "minutes", "seconds", "time", "timer", "watch" ] -}, { - "name" : "album", - "tags" : [ "album", "artist", "audio", "bvb", "cd", "computer", "data", "disk", "file", "music", "record", "sound", "storage", "track" ] -}, { - "name" : "local_dining", - "tags" : [ "dining", "eat", "food", "fork", "knife", "local", "meal", "restaurant", "spoon" ] -}, { - "name" : "headset", - "tags" : [ "accessory", "audio", "device", "ear", "earphone", "headphones", "headset", "listen", "music", "sound" ] -}, { - "name" : "maps_ugc", - "tags" : [ "+", "add", "bubble", "comment", "communicate", "feedback", "maps", "message", "new", "plus", "speech", "symbol", "ugc" ] -}, { - "name" : "airplane_ticket", - "tags" : [ "airplane", "airport", "boarding", "flight", "fly", "maps", "pass", "ticket", "transportation", "travel" ] -}, { - "name" : "vertical_split", - "tags" : [ "design", "format", "grid", "layout", "paragraph", "split", "text", "vertical", "website", "writing" ] -}, { - "name" : "sports_basketball", - "tags" : [ "athlete", "athletic", "ball", "basketball", "entertainment", "exercise", "game", "hobby", "social", "sports" ] -}, { - "name" : "next_plan", - "tags" : [ "arrow", "circle", "next", "plan", "right" ] -}, { - "name" : "drive_folder_upload", - "tags" : [ "arrow", "data", "doc", "document", "drive", "file", "folder", "sheet", "slide", "storage", "up", "upload" ] -}, { - "name" : "pregnant_woman", - "tags" : [ "baby", "birth", "body", "female", "human", "lady", "maternity", "mom", "mother", "people", "person", "pregnant", "women" ] -}, { - "name" : "wallpaper", - "tags" : [ "background", "image", "landscape", "photo", "photography", "picture", "wallpaper" ] -}, { - "name" : "image_search", - "tags" : [ "find", "glass", "image", "landscape", "look", "magnify", "magnifying", "mountain", "mountains", "photo", "photography", "picture", "search", "see" ] -}, { - "name" : "data_exploration", - "tags" : [ "analytics", "arrow", "chart", "data", "diagram", "exploration", "graph", "infographic", "measure", "metrics", "statistics", "tracking" ] -}, { - "name" : "device_thermostat", - "tags" : [ "celsius", "device", "fahrenheit", "meter", "temp", "temperature", "thermometer", "thermostat" ] -}, { - "name" : "healing", - "tags" : [ "bandage", "edit", "editing", "emergency", "fix", "healing", "hospital", "image", "medicine" ] -}, { - "name" : "laptop_mac", - "tags" : [ "Android", "OS", "chrome", "device", "display", "hardware", "iOS", "laptop", "mac", "monitor", "screen", "web", "window" ] -}, { - "name" : "height", - "tags" : [ "arrow", "color", "doc", "down", "edit", "editing", "editor", "fill", "format", "height", "paint", "sheet", "spreadsheet", "style", "text", "type", "up", "writing" ] -}, { - "name" : "restore_from_trash", - "tags" : [ "arrow", "back", "backwards", "clock", "date", "history", "refresh", "renew", "restore", "reverse", "rotate", "schedule", "time", "turn" ] -}, { - "name" : "radar", - "tags" : [ "detect", "military", "near", "network", "position", "radar", "scan" ] -}, { - "name" : "auto_awesome_motion", - "tags" : [ "adjust", "auto", "awesome", "collage", "edit", "editing", "enhance", "image", "motion", "photo", "video" ] -}, { - "name" : "file_download_done", - "tags" : [ "arrow", "arrows", "check", "done", "down", "download", "downloads", "drive", "file", "install", "installed", "tick", "upload" ] -}, { - "name" : "notification_add", - "tags" : [ "+", "active", "add", "alarm", "alert", "bell", "chime", "notification", "notifications", "notify", "plus", "reminder", "ring", "sound", "symbol" ] -}, { - "name" : "call_made", - "tags" : [ "arrow", "call", "device", "made", "mobile" ] -}, { - "name" : "camera_enhance", - "tags" : [ "ai", "artificial", "automatic", "automation", "camera", "custom", "enhance", "genai", "important", "intelligence", "lens", "magic", "photo", "photography", "picture", "quality", "smart", "spark", "sparkle", "special", "star" ] -}, { - "name" : "rotate_left", - "tags" : [ "around", "arrow", "direction", "inprogress", "left", "load", "loading refresh", "renew", "rotate", "turn" ] -}, { - "name" : "local_taxi", - "tags" : [ "automobile", "cab", "call", "car", "cars", "direction", "local", "lyft", "maps", "public", "taxi", "transportation", "uber", "vehicle", "yellow" ] -}, { - "name" : "star_border_purple500", - "tags" : [ "500", "best", "bookmark", "border", "favorite", "highlight", "outline", "purple", "ranking", "rate", "rating", "save", "star", "toggle" ] -}, { - "name" : "gpp_bad", - "tags" : [ "bad", "cancel", "certified", "close", "error", "exit", "gpp", "no", "privacy", "private", "protect", "protection", "remove", "security", "shield", "sim", "stop", "verified", "x" ] -}, { - "name" : "playlist_play", - "tags" : [ "arrow", "collection", "list", "music", "play", "playlist" ] -}, { - "name" : "cast", - "tags" : [ "Android", "OS", "airplay", "cast", "chrome", "connect", "desktop", "device", "display", "hardware", "iOS", "mac", "monitor", "screen", "screencast", "streaming", "television", "tv", "web", "window", "wireless" ] -}, { - "name" : "vertical_align_top", - "tags" : [ "align", "alignment", "arrow", "doc", "edit", "editing", "editor", "sheet", "spreadsheet", "text", "top", "type", "up", "vertical", "writing" ] -}, { - "name" : "ramen_dining", - "tags" : [ "breakfast", "dining", "dinner", "drink", "fastfood", "food", "lunch", "meal", "noodles", "ramen", "restaurant" ] -}, { - "name" : "data_usage", - "tags" : [ "analytics", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "statistics", "tracking", "usage" ] -}, { - "name" : "markunread_mailbox", - "tags" : [ "deliver", "envelop", "letter", "mail", "mailbox", "markunread", "post", "postal", "postbox", "receive", "send", "unread" ] -}, { - "name" : "terminal", - "tags" : [ "application", "code", "emulator", "program", "software", "terminal" ] -}, { - "name" : "screen_share", - "tags" : [ "Android", "OS", "arrow", "cast", "chrome", "device", "display", "hardware", "iOS", "laptop", "mac", "mirror", "monitor", "screen", "share", "steam", "streaming", "web", "window" ] -}, { - "name" : "center_focus_strong", - "tags" : [ "camera", "center", "focus", "image", "lens", "photo", "photography", "strong", "zoom" ] -}, { - "name" : "queue", - "tags" : [ "add", "collection", "layers", "list", "multiple", "music", "playlist", "queue", "stack", "stream", "video" ] -}, { - "name" : "games", - "tags" : [ "adjust", "arrow", "arrows", "control", "controller", "direction", "games", "gaming", "left", "move", "right" ] -}, { - "name" : "low_priority", - "tags" : [ "arrange", "arrow", "backward", "bottom", "list", "low", "move", "order", "priority" ] -}, { - "name" : "dynamic_form", - "tags" : [ "bolt", "code", "dynamic", "electric", "fast", "form", "lightning", "lists", "questionnaire", "thunderbolt" ] -}, { - "name" : "tab", - "tags" : [ "browser", "computer", "document", "documents", "folder", "internet", "tab", "tabs", "web", "website", "window", "windows" ] -}, { - "name" : "lock_reset", - "tags" : [ "around", "inprogress", "load", "loading refresh", "lock", "locked", "password", "privacy", "private", "protection", "renew", "rotate", "safety", "secure", "security", "turn" ] -}, { - "name" : "room_preferences", - "tags" : [ "building", "door", "doorway", "entrance", "gear", "home", "house", "interior", "office", "open", "preferences", "room", "settings" ] -}, { - "name" : "crop", - "tags" : [ "adjust", "adjustments", "area", "crop", "edit", "editing", "frame", "image", "images", "photo", "photos", "rectangle", "settings", "size", "square" ] -}, { - "name" : "monitor_weight", - "tags" : [ "body", "device", "diet", "health", "monitor", "scale", "smart", "weight" ] -}, { - "name" : "trip_origin", - "tags" : [ "circle", "departure", "origin", "trip" ] -}, { - "name" : "calendar_view_week", - "tags" : [ "calendar", "date", "day", "event", "format", "grid", "layout", "month", "schedule", "today", "view", "week" ] -}, { - "name" : "signal_wifi_4_bar", - "tags" : [ "4", "bar", "cell", "cellular", "data", "internet", "mobile", "network", "phone", "signal", "wifi", "wireless" ] -}, { - "name" : "blur_on", - "tags" : [ "blur", "disabled", "dots", "edit", "editing", "effect", "enabled", "enhance", "filter", "off", "on", "slash" ] -}, { - "name" : "view_stream", - "tags" : [ "design", "format", "grid", "layout", "lines", "list", "stacked", "stream", "view", "website" ] -}, { - "name" : "radio", - "tags" : [ "antenna", "audio", "device", "frequency", "hardware", "listen", "media", "music", "player", "radio", "signal", "tune" ] -}, { - "name" : "hail", - "tags" : [ "body", "hail", "human", "people", "person", "pick", "public", "stop", "taxi", "transportation" ] -}, { - "name" : "do_disturb_on", - "tags" : [ "cancel", "close", "denied", "deny", "disabled", "disturb", "do", "enabled", "off", "on", "remove", "silence", "slash", "stop" ] -}, { - "name" : "sensor_door", - "tags" : [ "alarm", "security", "security system" ] -}, { - "name" : "wb_incandescent", - "tags" : [ "balance", "bright", "edit", "editing", "incandescent", "light", "lighting", "setting", "settings", "white", "wp" ] -}, { - "name" : "local_drink", - "tags" : [ "cup", "drink", "drop", "droplet", "liquid", "local", "park", "water" ] -}, { - "name" : "accessible_forward", - "tags" : [ "accessibility", "accessible", "body", "forward", "handicap", "help", "human", "people", "person", "wheelchair" ] -}, { - "name" : "replay_circle_filled", - "tags" : [ "arrow", "arrows", "circle", "control", "controls", "filled", "music", "refresh", "renew", "repeat", "replay", "video" ] -}, { - "name" : "local_printshop", - "tags" : [ "draft", "fax", "ink", "local", "machine", "office", "paper", "print", "printer", "printshop", "send" ] -}, { - "name" : "local_laundry_service", - "tags" : [ "cleaning", "clothing", "dry", "dryer", "hotel", "laundry", "local", "service", "washer" ] -}, { - "name" : "vpn_lock", - "tags" : [ "earth", "globe", "lock", "locked", "network", "password", "privacy", "private", "protection", "safety", "secure", "security", "virtual", "vpn", "world" ] -}, { - "name" : "schema", - "tags" : [ "analytics", "chart", "data", "diagram", "flow", "graph", "infographic", "measure", "metrics", "schema", "statistics", "tracking" ] -}, { - "name" : "request_page", - "tags" : [ "data", "doc", "document", "drive", "file", "folder", "folders", "page", "paper", "request", "sheet", "slide", "writing" ] -}, { - "name" : "token", - "tags" : [ "badge", "hexagon", "mark", "shield", "sign", "symbol" ] -}, { - "name" : "branding_watermark", - "tags" : [ "branding", "components", "copyright", "design", "emblem", "format", "identity", "interface", "layout", "logo", "screen", "site", "stamp", "ui", "ux", "watermark", "web", "website", "window" ] -}, { - "name" : "theater_comedy", - "tags" : [ "broadway", "comedy", "event", "movie", "musical", "places", "show", "standup", "theater", "tour", "watch" ] -}, { - "name" : "text_format", - "tags" : [ "alphabet", "character", "font", "format", "letter", "square A", "style", "symbol", "text", "type" ] -}, { - "name" : "directions_bus_filled", - "tags" : [ "automobile", "bus", "car", "cars", "direction", "directions", "filled", "maps", "public", "transportation", "vehicle" ] -}, { - "name" : "remove_done", - "tags" : [ "approve", "check", "complete", "disabled", "done", "enabled", "finished", "mark", "multiple", "off", "ok", "on", "remove", "select", "slash", "tick", "yes" ] -}, { - "name" : "sports_bar", - "tags" : [ "alcohol", "bar", "beer", "drink", "liquor", "pint", "places", "pub", "sports" ] -}, { - "name" : "watch", - "tags" : [ "Android", "OS", "ar", "clock", "gadget", "iOS", "time", "vr", "watch", "wearables", "web", "wristwatch" ] -}, { - "name" : "add_to_drive", - "tags" : [ "add", "app", "application", "backup", "cloud", "drive", "files", "folders", "gdrive", "google", "recovery", "shortcut", "storage" ] -}, { - "name" : "format_align_center", - "tags" : [ "align", "alignment", "center", "doc", "edit", "editing", "editor", "format", "sheet", "spreadsheet", "text", "type", "writing" ] -}, { - "name" : "settings_power", - "tags" : [ "info", "information", "off", "on", "power", "save", "settings", "shutdown" ] -}, { - "name" : "local_pizza", - "tags" : [ "drink", "fastfood", "food", "local", "meal", "pizza" ] -}, { - "name" : "add_alert", - "tags" : [ "+", "active", "add", "alarm", "alert", "bell", "chime", "new", "notifications", "notify", "plus", "reminder", "ring", "sound", "symbol" ] -}, { - "name" : "smart_button", - "tags" : [ "action", "ai", "artificial", "automatic", "automation", "button", "components", "composer", "custom", "function", "genai", "intelligence", "interface", "magic", "site", "smart", "spark", "sparkle", "special", "star", "stars", "ui", "ux", "web", "website" ] -}, { - "name" : "flare", - "tags" : [ "bright", "edit", "editing", "effect", "flare", "image", "images", "light", "photography", "picture", "pictures", "sun" ] -}, { - "name" : "developer_mode", - "tags" : [ "Android", "OS", "bracket", "cell", "code", "developer", "development", "device", "engineer", "hardware", "iOS", "mobile", "mode", "phone", "tablet" ] -}, { - "name" : "call_split", - "tags" : [ "arrow", "call", "device", "mobile", "split" ] -}, { - "name" : "free_breakfast", - "tags" : [ "beverage", "breakfast", "cafe", "coffee", "cup", "drink", "free", "mug", "tea" ] -}, { - "name" : "auto_delete", - "tags" : [ "auto", "bin", "can", "clock", "date", "delete", "garbage", "remove", "schedule", "time", "trash" ] -}, { - "name" : "sports_kabaddi", - "tags" : [ "athlete", "athletic", "body", "combat", "entertainment", "exercise", "fighting", "game", "hobby", "human", "kabaddi", "people", "person", "social", "sports", "wrestle", "wrestling" ] -}, { - "name" : "face_retouching_natural", - "tags" : [ "ai", "artificial", "automatic", "automation", "custom", "edit", "editing", "effect", "emoji", "emotion", "face", "faces", "genai", "image", "intelligence", "magic", "natural", "photo", "photography", "retouch", "retouching", "settings", "smart", "spark", "sparkle", "star", "tag" ] -}, { - "name" : "not_listed_location", - "tags" : [ "?", "assistance", "destination", "direction", "help", "info", "information", "listed", "location", "maps", "not", "pin", "place", "punctuation", "question mark", "stop", "support", "symbol" ] -}, { - "name" : "wb_cloudy", - "tags" : [ "balance", "cloud", "cloudy", "edit", "editing", "white", "wp" ] -}, { - "name" : "sports", - "tags" : [ "athlete", "athletic", "blowing", "coach", "entertainment", "exercise", "game", "hobby", "instrument", "referee", "social", "sound", "sports", "warning", "whistle" ] -}, { - "name" : "emoji_symbols", - "tags" : [ "ampersand", "character", "emoji", "hieroglyph", "music", "note", "percent", "sign", "symbols" ] -}, { - "name" : "bathtub", - "tags" : [ "bath", "bathing", "bathroom", "bathtub", "home", "hotel", "human", "person", "shower", "travel", "tub" ] -}, { - "name" : "forward_10", - "tags" : [ "10", "arrow", "control", "controls", "digit", "fast", "forward", "music", "number", "play", "seconds", "symbol", "video" ] -}, { - "name" : "tablet_mac", - "tags" : [ "Android", "OS", "device", "hardware", "iOS", "ipad", "mobile", "tablet mac", "web" ] -}, { - "name" : "mode_night", - "tags" : [ "dark", "disturb", "lunar", "mode", "moon", "night", "sleep" ] -}, { - "name" : "broken_image", - "tags" : [ "broken", "corrupt", "error", "image", "landscape", "mountain", "mountains", "photo", "photography", "picture", "torn" ] -}, { - "name" : "escalator_warning", - "tags" : [ "body", "child", "escalator", "human", "kid", "parent", "people", "person", "warning" ] -}, { - "name" : "assistant", - "tags" : [ "ai", "artificial", "assistant", "automatic", "automation", "bubble", "chat", "comment", "communicate", "custom", "feedback", "genai", "intelligence", "magic", "message", "recommendation", "smart", "spark", "sparkle", "speech", "star", "suggestion", "twinkle" ] -}, { - "name" : "cases", - "tags" : [ "bag", "baggage", "briefcase", "business", "case", "cases", "purse", "suitcase" ] -}, { - "name" : "wifi_tethering", - "tags" : [ "cell", "cellular", "connection", "data", "internet", "mobile", "network", "phone", "scan", "service", "signal", "speed", "tethering", "wifi", "wireless" ] -}, { - "name" : "reduce_capacity", - "tags" : [ "arrow", "body", "capacity", "covid", "decrease", "down", "human", "people", "person", "reduce", "social" ] -}, { - "name" : "colorize", - "tags" : [ "color", "colorize", "dropper", "extract", "eye", "picker", "tool" ] -}, { - "name" : "save_as", - "tags" : [ "compose", "create", "data", "disk", "document", "draft", "drive", "edit", "editing", "file", "floppy", "input", "multimedia", "pen", "pencil", "save", "storage", "write", "writing" ] -}, { - "name" : "card_travel", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "membership", "miles", "money", "online", "pay", "payment", "travel", "trip" ] -}, { - "name" : "emoji_food_beverage", - "tags" : [ "beverage", "coffee", "cup", "drink", "emoji", "mug", "plate", "set", "tea" ] -}, { - "name" : "font_download", - "tags" : [ "A", "alphabet", "character", "download", "font", "letter", "square", "symbol", "text", "type" ] -}, { - "name" : "outbox", - "tags" : [ "box", "mail", "outbox", "send", "sent" ] -}, { - "name" : "battery_std", - "tags" : [ "battery", "cell", "charge", "mobile", "plus", "power", "standard", "std" ] -}, { - "name" : "sick", - "tags" : [ "covid", "discomfort", "emotions", "expressions", "face", "feelings", "fever", "flu", "ill", "mood", "pain", "person", "sick", "survey", "upset" ] -}, { - "name" : "add_location", - "tags" : [ "+", "add", "destination", "direction", "location", "maps", "new", "pin", "place", "plus", "stop", "symbol" ] -}, { - "name" : "try", - "tags" : [ "bookmark", "bubble", "chat", "comment", "communicate", "favorite", "feedback", "highlight", "important", "marked", "message", "save", "saved", "shape", "special", "speech", "star", "try" ] -}, { - "name" : "discount", - "tags" : [ ] -}, { - "name" : "man", - "tags" : [ "boy", "gender", "male", "man", "social", "symbol" ] -}, { - "name" : "running_with_errors", - "tags" : [ "!", "alert", "attention", "caution", "danger", "duration", "error", "errors", "exclamation", "important", "mark", "notification", "process", "processing", "running", "symbol", "time", "warning", "with" ] -}, { - "name" : "diversity_3", - "tags" : [ "committee", "diverse", "diversity", "family", "friends", "group", "groups", "humans", "network", "people", "persons", "social", "team" ] -}, { - "name" : "filter_none", - "tags" : [ "filter", "multiple", "none", "square", "stack" ] -}, { - "name" : "cloud_sync", - "tags" : [ "app", "application", "around", "backup", "cloud", "connection", "drive", "files", "folders", "inprogress", "internet", "load", "loading refresh", "network", "renew", "rotate", "sky", "storage", "turn", "upload" ] -}, { - "name" : "bloodtype", - "tags" : [ "blood", "bloodtype", "donate", "droplet", "emergency", "hospital", "medicine", "negative", "positive", "type", "water" ] -}, { - "name" : "dinner_dining", - "tags" : [ "breakfast", "dining", "dinner", "food", "fork", "lunch", "meal", "restaurant", "spaghetti", "utensils" ] -}, { - "name" : "transfer_within_a_station", - "tags" : [ "a", "arrow", "arrows", "body", "direction", "human", "left", "maps", "people", "person", "public", "right", "route", "station", "stop", "transfer", "transportation", "vehicle", "walk", "within" ] -}, { - "name" : "weekend", - "tags" : [ "chair", "couch", "furniture", "home", "living", "lounge", "relax", "room", "weekend" ] -}, { - "name" : "child_friendly", - "tags" : [ "baby", "care", "carriage", "child", "children", "friendly", "infant", "kid", "newborn", "stroller", "toddler", "young" ] -}, { - "name" : "offline_pin", - "tags" : [ "approve", "check", "checkmark", "circle", "complete", "done", "mark", "offline", "ok", "pin", "select", "tick", "validate", "verified", "yes" ] -}, { - "name" : "replay_10", - "tags" : [ "10", "arrow", "arrows", "control", "controls", "digit", "music", "number", "refresh", "renew", "repeat", "replay", "symbol", "ten", "video" ] -}, { - "name" : "brightness_4", - "tags" : [ "4", "brightness", "circle", "control", "crescent", "level", "moon", "screen", "sun" ] -}, { - "name" : "cruelty_free", - "tags" : [ "animal", "bunny", "cruelty", "eco", "free", "nature", "rabbit", "social", "sustainability", "sustainable", "testing" ] -}, { - "name" : "format_paint", - "tags" : [ "brush", "color", "doc", "edit", "editing", "editor", "fill", "format", "paint", "roller", "sheet", "spreadsheet", "style", "text", "type", "writing" ] -}, { - "name" : "filter_center_focus", - "tags" : [ "camera", "center", "dot", "edit", "filter", "focus", "image", "photo", "photography", "picture" ] -}, { - "name" : "area_chart", - "tags" : [ "analytics", "area", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "statistics", "tracking" ] -}, { - "name" : "bakery_dining", - "tags" : [ "bakery", "bread", "breakfast", "brunch", "croissant", "dining", "food" ] -}, { - "name" : "emoji_transportation", - "tags" : [ "architecture", "automobile", "building", "car", "cars", "direction", "emoji", "estate", "maps", "place", "public", "real", "residence", "residential", "shelter", "transportation", "travel", "vehicle" ] -}, { - "name" : "folder_special", - "tags" : [ "bookmark", "data", "doc", "document", "drive", "favorite", "file", "folder", "highlight", "important", "marked", "save", "saved", "shape", "sheet", "slide", "special", "star", "storage" ] -}, { - "name" : "door_front", - "tags" : [ "closed", "door", "doorway", "entrance", "exit", "front", "home", "house", "way" ] -}, { - "name" : "calendar_view_day", - "tags" : [ "calendar", "date", "day", "event", "format", "grid", "layout", "month", "schedule", "today", "view", "week" ] -}, { - "name" : "legend_toggle", - "tags" : [ "analytics", "chart", "data", "diagram", "graph", "infographic", "legend", "measure", "metrics", "monitoring", "stackdriver", "statistics", "toggle", "tracking" ] -}, { - "name" : "light", - "tags" : [ "bulb", "ceiling", "hanging", "inside", "interior", "lamp", "light", "lighting", "pendent", "room" ] -}, { - "name" : "find_replace", - "tags" : [ "around", "arrows", "find", "glass", "inprogress", "load", "loading refresh", "look", "magnify", "magnifying", "renew", "replace", "rotate", "search", "see" ] -}, { - "name" : "crop_original", - "tags" : [ "adjust", "adjustments", "area", "crop", "edit", "editing", "frame", "image", "images", "original", "photo", "photos", "picture", "settings", "size" ] -}, { - "name" : "rowing", - "tags" : [ "activity", "boat", "body", "canoe", "human", "people", "person", "row", "rowing", "sport", "water" ] -}, { - "name" : "enhanced_encryption", - "tags" : [ "+", "add", "encryption", "enhanced", "lock", "locked", "new", "password", "plus", "privacy", "private", "protection", "safety", "secure", "security", "symbol" ] -}, { - "name" : "how_to_vote", - "tags" : [ "ballot", "election", "how", "poll", "to", "vote" ] -}, { - "name" : "chrome_reader_mode", - "tags" : [ "chrome", "mode", "read", "reader", "text" ] -}, { - "name" : "auto_fix_normal", - "tags" : [ "ai", "artificial", "auto", "automatic", "automation", "custom", "edit", "erase", "fix", "genai", "intelligence", "magic", "modify", "smart", "spark", "sparkle", "star", "wand" ] -}, { - "name" : "compress", - "tags" : [ "arrow", "arrows", "collide", "compress", "pressure", "push", "together" ] -}, { - "name" : "dehaze", - "tags" : [ "adjust", "dehaze", "edit", "editing", "enhance", "haze", "image", "lines", "photo", "photography", "remove" ] -}, { - "name" : "outlet", - "tags" : [ "connect", "connecter", "electricity", "outlet", "plug", "power" ] -}, { - "name" : "desktop_mac", - "tags" : [ "Android", "OS", "chrome", "desktop", "device", "display", "hardware", "iOS", "mac", "monitor", "screen", "web", "window" ] -}, { - "name" : "nature_people", - "tags" : [ "activity", "body", "forest", "human", "nature", "outdoor", "outside", "park", "people", "person", "tree", "wilderness" ] -}, { - "name" : "sports_tennis", - "tags" : [ "athlete", "athletic", "ball", "bat", "entertainment", "exercise", "game", "hobby", "racket", "social", "sports", "tennis" ] -}, { - "name" : "forest", - "tags" : [ "forest", "jungle", "nature", "plantation", "plants", "trees", "woodland" ] -}, { - "name" : "upcoming", - "tags" : [ "alarm", "calendar", "mail", "message", "notification", "upcoming" ] -}, { - "name" : "assignment_returned", - "tags" : [ "arrow", "assignment", "clipboard", "doc", "document", "down", "returned" ] -}, { - "name" : "cookie", - "tags" : [ "biscuit", "cookies", "data", "dessert", "wafer" ] -}, { - "name" : "fax", - "tags" : [ "fax", "machine", "office", "phone", "send" ] -}, { - "name" : "square", - "tags" : [ "draw", "four", "shape quadrangle", "sides", "square" ] -}, { - "name" : "density_medium", - "tags" : [ "density", "horizontal", "lines", "medium", "rule", "rules" ] -}, { - "name" : "terrain", - "tags" : [ "geography", "landscape", "mountain", "terrain" ] -}, { - "name" : "settings_brightness", - "tags" : [ "brightness", "dark", "filter", "light", "mode", "setting", "settings" ] -}, { - "name" : "attach_email", - "tags" : [ "attach", "attachment", "clip", "compose", "email", "envelop", "letter", "link", "mail", "message", "send" ] -}, { - "name" : "photo", - "tags" : [ "image", "mountain", "mountains", "photo", "photography", "picture" ] -}, { - "name" : "http", - "tags" : [ "alphabet", "character", "font", "http", "letter", "symbol", "text", "transfer", "type", "url", "website" ] -}, { - "name" : "garage", - "tags" : [ "automobile", "automotive", "car", "cars", "direction", "garage", "maps", "transportation", "travel", "vehicle" ] -}, { - "name" : "wine_bar", - "tags" : [ "alcohol", "bar", "cocktail", "cup", "drink", "glass", "liquor", "wine" ] -}, { - "name" : "multiple_stop", - "tags" : [ "arrows", "directions", "dots", "left", "maps", "multiple", "navigation", "right", "stop" ] -}, { - "name" : "format_color_text", - "tags" : [ "color", "doc", "edit", "editing", "editor", "fill", "format", "paint", "sheet", "spreadsheet", "style", "text", "type", "writing" ] -}, { - "name" : "gesture", - "tags" : [ "drawing", "finger", "gesture", "gestures", "hand", "motion" ] -}, { - "name" : "heart_broken", - "tags" : [ "break", "broken", "core", "crush", "health", "heart", "nucleus", "split" ] -}, { - "name" : "format_align_right", - "tags" : [ "align", "alignment", "doc", "edit", "editing", "editor", "format", "right", "sheet", "spreadsheet", "text", "type", "writing" ] -}, { - "name" : "transgender", - "tags" : [ "female", "gender", "lgbt", "male", "neutral", "social", "symbol", "transgender" ] -}, { - "name" : "alarm_add", - "tags" : [ "+", "add", "alarm", "alert", "bell", "clock", "countdown", "date", "new", "notification", "plus", "schedule", "symbol", "time" ] -}, { - "name" : "new_label", - "tags" : [ "+", "add", "archive", "bookmark", "favorite", "label", "library", "new", "plus", "read", "reading", "remember", "ribbon", "save", "symbol", "tag" ] -}, { - "name" : "south_east", - "tags" : [ "arrow", "directional", "down", "east", "maps", "navigation", "right", "south" ] -}, { - "name" : "backup_table", - "tags" : [ "backup", "drive", "files folders", "format", "layout", "stack", "storage", "table" ] -}, { - "name" : "unsubscribe", - "tags" : [ "cancel", "close", "email", "envelop", "letter", "mail", "message", "newsletter", "off", "remove", "send", "subscribe", "unsubscribe" ] -}, { - "name" : "flash_off", - "tags" : [ "bolt", "disabled", "electric", "enabled", "fast", "flash", "lightning", "off", "on", "slash", "thunderbolt" ] -}, { - "name" : "elderly", - "tags" : [ "body", "cane", "elderly", "human", "old", "people", "person", "senior" ] -}, { - "name" : "generating_tokens", - "tags" : [ "access", "ai", "api", "artificial", "automatic", "automation", "coin", "custom", "genai", "generating", "intelligence", "magic", "smart", "spark", "sparkle", "star", "tokens" ] -}, { - "name" : "spellcheck", - "tags" : [ "a", "alphabet", "approve", "character", "check", "font", "letter", "mark", "ok", "processor", "select", "spell", "spellcheck", "symbol", "text", "tick", "type", "word", "write", "yes" ] -}, { - "name" : "auto_awesome_mosaic", - "tags" : [ "adjust", "auto", "awesome", "collage", "edit", "editing", "enhance", "image", "mosaic", "photo" ] -}, { - "name" : "outdoor_grill", - "tags" : [ "barbecue", "bbq", "charcoal", "cooking", "grill", "home", "house", "outdoor", "outside" ] -}, { - "name" : "restore_page", - "tags" : [ "arrow", "data", "doc", "file", "page", "paper", "refresh", "restore", "rotate", "sheet", "storage" ] -}, { - "name" : "foundation", - "tags" : [ "architecture", "base", "basis", "building", "construction", "estate", "foundation", "home", "house", "real", "residential" ] -}, { - "name" : "credit_card_off", - "tags" : [ "card", "charge", "commerce", "cost", "credit", "disabled", "enabled", "finance", "money", "off", "online", "pay", "payment", "slash" ] -}, { - "name" : "scatter_plot", - "tags" : [ "analytics", "bar", "bars", "chart", "circles", "data", "diagram", "dot", "graph", "infographic", "measure", "metrics", "plot", "scatter", "statistics", "tracking" ] -}, { - "name" : "signal_cellular_4_bar", - "tags" : [ "4", "bar", "cell", "cellular", "data", "internet", "mobile", "network", "phone", "signal", "speed", "wifi", "wireless" ] -}, { - "name" : "add_moderator", - "tags" : [ "+", "add", "certified", "moderator", "new", "plus", "privacy", "private", "protect", "protection", "security", "shield", "symbol", "verified" ] -}, { - "name" : "play_for_work", - "tags" : [ "arrow", "circle", "down", "google", "half", "play", "work" ] -}, { - "name" : "add_card", - "tags" : [ "+", "add", "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "money", "new", "online", "pay", "payment", "plus", "price", "shopping", "symbol" ] -}, { - "name" : "app_settings_alt", - "tags" : [ "Android", "OS", "app", "applications", "cell", "device", "gear", "hardware", "iOS", "mobile", "phone", "setting", "settings", "tablet" ] -}, { - "name" : "keyboard_tab", - "tags" : [ "arrow", "keyboard", "left", "next", "right", "tab" ] -}, { - "name" : "wifi_protected_setup", - "tags" : [ "around", "arrow", "arrows", "protected", "rotate", "setup", "wifi" ] -}, { - "name" : "deck", - "tags" : [ "chairs", "deck", "home", "house", "outdoors", "outside", "patio", "social", "terrace", "umbrella", "yard" ] -}, { - "name" : "takeout_dining", - "tags" : [ "box", "container", "delivery", "dining", "food", "meal", "restaurant", "takeout" ] -}, { - "name" : "tag_faces", - "tags" : [ "emoji", "emotion", "faces", "happy", "satisfied", "smile", "tag" ] -}, { - "name" : "brightness_6", - "tags" : [ "6", "brightness", "circle", "control", "crescent", "level", "moon", "screen", "sun" ] -}, { - "name" : "woman", - "tags" : [ "female", "gender", "girl", "lady", "social", "symbol", "woman", "women" ] -}, { - "name" : "assistant_direction", - "tags" : [ "assistant", "destination", "direction", "location", "maps", "navigate", "navigation", "pin", "place", "right", "stop" ] -}, { - "name" : "brightness_5", - "tags" : [ "5", "brightness", "circle", "control", "crescent", "level", "moon", "screen", "sun" ] -}, { - "name" : "social_distance", - "tags" : [ "6", "apart", "body", "distance", "ft", "human", "people", "person", "social", "space" ] -}, { - "name" : "free_cancellation", - "tags" : [ "approve", "calendar", "cancel", "cancellation", "check", "complete", "date", "day", "done", "event", "exit", "free", "mark", "month", "no", "ok", "remove", "schedule", "select", "stop", "tick", "validate", "verified", "x", "yes" ] -}, { - "name" : "subdirectory_arrow_left", - "tags" : [ "arrow", "directory", "down", "left", "navigation", "sub", "subdirectory" ] -}, { - "name" : "laptop_chromebook", - "tags" : [ "Android", "OS", "chrome", "chromebook", "device", "display", "hardware", "iOS", "laptop", "mac chromebook", "monitor", "screen", "web", "window" ] -}, { - "name" : "format_list_numbered_rtl", - "tags" : [ "align", "alignment", "digit", "doc", "edit", "editing", "editor", "format", "list", "notes", "number", "numbered", "rtl", "sheet", "spreadsheet", "symbol", "text", "type", "writing" ] -}, { - "name" : "store_mall_directory", - "tags" : [ "directory", "mall", "store" ] -}, { - "name" : "settings_overscan", - "tags" : [ "arrows", "expand", "image", "photo", "picture", "scan", "settings" ] -}, { - "name" : "icecream", - "tags" : [ "cream", "dessert", "food", "ice", "icecream", "snack" ] -}, { - "name" : "details", - "tags" : [ "details", "edit", "editing", "enhance", "image", "photo", "photography", "sharpen", "triangle" ] -}, { - "name" : "add_reaction", - "tags" : [ "+", "add", "emoji", "emotions", "expressions", "face", "feelings", "glad", "happiness", "happy", "icon", "icons", "insert", "like", "mood", "new", "person", "pleased", "plus", "smile", "smiling", "social", "survey", "symbol" ] -}, { - "name" : "follow_the_signs", - "tags" : [ "arrow", "body", "directional", "follow", "human", "people", "person", "right", "signs", "social", "the" ] -}, { - "name" : "attribution", - "tags" : [ "attribute", "attribution", "body", "copyright", "copywriter", "human", "people", "person" ] -}, { - "name" : "food_bank", - "tags" : [ "architecture", "bank", "building", "charity", "eat", "estate", "food", "fork", "house", "knife", "meal", "place", "real", "residence", "residential", "shelter", "utensils" ] -}, { - "name" : "closed_caption", - "tags" : [ "accessible", "alphabet", "caption", "cc", "character", "closed", "decoder", "font", "language", "letter", "media", "movies", "subtitle", "subtitles", "symbol", "text", "tv", "type" ] -}, { - "name" : "gif", - "tags" : [ "alphabet", "animated", "animation", "bitmap", "character", "font", "format", "gif", "graphics", "interchange", "letter", "symbol", "text", "type" ] -}, { - "name" : "phonelink", - "tags" : [ "Android", "OS", "chrome", "computer", "connect", "desktop", "device", "hardware", "iOS", "link", "mac", "mobile", "phone", "phonelink", "sync", "tablet", "web", "windows" ] -}, { - "name" : "grain", - "tags" : [ "dots", "edit", "editing", "effect", "filter", "grain", "image", "images", "photography", "picture", "pictures" ] -}, { - "name" : "personal_injury", - "tags" : [ "accident", "aid", "arm", "bandage", "body", "broke", "cast", "fracture", "health", "human", "injury", "medical", "patient", "people", "person", "personal", "sling", "social" ] -}, { - "name" : "flip_camera_android", - "tags" : [ "android", "camera", "center", "edit", "editing", "flip", "image", "mobile", "orientation", "rotate", "turn" ] -}, { - "name" : "museum", - "tags" : [ "architecture", "attraction", "building", "estate", "event", "exhibition", "explore", "local", "museum", "places", "real", "see", "shop", "store", "tour" ] -}, { - "name" : "north_west", - "tags" : [ "arrow", "directional", "left", "maps", "navigation", "north", "up", "west" ] -}, { - "name" : "gite", - "tags" : [ "architecture", "estate", "gite", "home", "hostel", "house", "maps", "place", "real", "residence", "residential", "stay", "traveling" ] -}, { - "name" : "highlight", - "tags" : [ "color", "doc", "edit", "editing", "editor", "emphasize", "fill", "flash", "format", "highlight", "light", "paint", "sheet", "spreadsheet", "style", "text", "type", "writing" ] -}, { - "name" : "brightness_1", - "tags" : [ "1", "brightness", "circle", "control", "crescent", "level", "moon", "screen" ] -}, { - "name" : "plus_one", - "tags" : [ "1", "add", "digit", "increase", "number", "one", "plus", "symbol" ] -}, { - "name" : "villa", - "tags" : [ "architecture", "beach", "estate", "home", "house", "maps", "place", "real", "residence", "residential", "traveling", "vacation stay", "villa" ] -}, { - "name" : "fmd_bad", - "tags" : [ "!", "alert", "attention", "bad", "caution", "danger", "destination", "direction", "error", "exclamation", "fmd", "important", "location", "maps", "mark", "notification", "pin", "place", "symbol", "warning" ] -}, { - "name" : "flashlight_on", - "tags" : [ "disabled", "enabled", "flash", "flashlight", "light", "off", "on", "slash" ] -}, { - "name" : "flip", - "tags" : [ "edit", "editing", "flip", "image", "orientation", "scan scanning" ] -}, { - "name" : "nightlife", - "tags" : [ "alcohol", "bar", "bottle", "club", "cocktail", "dance", "drink", "food", "glass", "liquor", "music", "nightlife", "note", "wine" ] -}, { - "name" : "present_to_all", - "tags" : [ "all", "arrow", "present", "presentation", "screen", "share", "site", "slides", "to", "web", "website" ] -}, { - "name" : "do_disturb", - "tags" : [ "cancel", "close", "denied", "deny", "disturb", "do", "remove", "silence", "stop" ] -}, { - "name" : "outbound", - "tags" : [ "arrow", "circle", "directional", "outbound", "right", "up" ] -}, { - "name" : "local_pharmacy", - "tags" : [ "911", "aid", "cross", "emergency", "first", "hospital", "local", "medicine", "pharmacy", "places" ] -}, { - "name" : "splitscreen", - "tags" : [ "column", "grid", "layout", "multitasking", "row", "screen", "split", "splitscreen", "two" ] -}, { - "name" : "waterfall_chart", - "tags" : [ "analytics", "bar", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "statistics", "tracking", "waterfall" ] -}, { - "name" : "switch_left", - "tags" : [ "arrows", "directional", "left", "navigation", "switch", "toggle" ] -}, { - "name" : "domain_verification", - "tags" : [ "app", "application desktop", "approve", "check", "complete", "design", "domain", "done", "interface", "internet", "layout", "mark", "ok", "screen", "select", "site", "tick", "ui", "ux", "validate", "verification", "verified", "web", "website", "window", "www", "yes" ] -}, { - "name" : "fireplace", - "tags" : [ "chimney", "fire", "fireplace", "flame", "home", "house", "living", "pit", "place", "room", "warm", "winter" ] -}, { - "name" : "video_settings", - "tags" : [ "change", "details", "gear", "info", "information", "options", "play", "screen", "service", "setting", "settings", "video", "window" ] -}, { - "name" : "disabled_visible", - "tags" : [ "cancel", "close", "disabled", "exit", "eye", "no", "on", "quit", "remove", "reveal", "see", "show", "stop", "view", "visibility", "visible" ] -}, { - "name" : "network_wifi", - "tags" : [ "cell", "cellular", "data", "internet", "mobile", "network", "phone", "speed", "wifi", "wireless" ] -}, { - "name" : "quickreply", - "tags" : [ "bolt", "bubble", "chat", "comment", "communicate", "fast", "lightning", "message", "quick", "quickreply", "reply", "speech", "thunderbolt" ] -}, { - "name" : "swap_vertical_circle", - "tags" : [ "arrow", "arrows", "circle", "down", "swap", "up", "vertical" ] -}, { - "name" : "format_align_justify", - "tags" : [ "align", "alignment", "density", "doc", "edit", "editing", "editor", "extra", "format", "justify", "sheet", "small", "spreadsheet", "text", "type", "writing" ] -}, { - "name" : "settings_input_composite", - "tags" : [ "component", "composite", "connection", "connectivity", "input", "plug", "points", "settings" ] -}, { - "name" : "loupe", - "tags" : [ "+", "add", "details", "focus", "glass", "loupe", "magnifying", "new", "plus", "symbol" ] -}, { - "name" : "123", - "tags" : [ "1", "2", "3", "digit", "number", "symbol" ] -}, { - "name" : "network_check", - "tags" : [ "check", "connect", "connection", "internet", "meter", "network", "signal", "speed", "tick", "wifi", "wireless" ] -}, { - "name" : "sms_failed", - "tags" : [ "!", "alert", "attention", "bubbles", "caution", "chat", "communication", "conversation", "danger", "error", "exclamation", "failed", "feedback", "important", "mark", "message", "notification", "service", "sms", "speech", "symbol", "warning" ] -}, { - "name" : "cancel_schedule_send", - "tags" : [ "cancel", "email", "mail", "no", "quit", "remove", "schedule", "send", "share", "stop", "x" ] -}, { - "name" : "work_history", - "tags" : [ "back", "backwards", "bag", "baggage", "briefcase", "business", "case", "clock", "date", "history", "job", "pending", "recent", "schedule", "suitcase", "time", "updates", "work" ] -}, { - "name" : "electric_bolt", - "tags" : [ "bolt", "electric", "energy", "fast", "lightning", "nest", "thunderbolt" ] -}, { - "name" : "view_day", - "tags" : [ "cards", "carousel", "day", "design", "format", "grid", "layout", "view", "website" ] -}, { - "name" : "night_shelter", - "tags" : [ "architecture", "bed", "building", "estate", "homeless", "house", "night", "place", "real", "shelter", "sleep" ] -}, { - "name" : "monitor", - "tags" : [ "Android", "OS", "chrome", "device", "display", "hardware", "iOS", "mac", "monitor", "screen", "web", "window" ] -}, { - "name" : "clean_hands", - "tags" : [ "bacteria", "clean", "disinfect", "germs", "gesture", "hand", "hands", "sanitize", "sanitizer" ] -}, { - "name" : "mark_chat_read", - "tags" : [ "approve", "bubble", "chat", "check", "comment", "communicate", "complete", "done", "mark", "message", "ok", "read", "select", "sent", "speech", "tick", "verified", "yes" ] -}, { - "name" : "comment_bank", - "tags" : [ "archive", "bank", "bookmark", "bubble", "cchat", "comment", "communicate", "favorite", "label", "library", "message", "remember", "ribbon", "save", "speech", "tag" ] -}, { - "name" : "sim_card_download", - "tags" : [ "arrow", "camera", "card", "chip", "device", "down", "download", "memory", "phone", "sim", "storage" ] -}, { - "name" : "lan", - "tags" : [ "computer", "connection", "data", "internet", "lan", "network", "service" ] -}, { - "name" : "piano", - "tags" : [ "instrument", "keyboard", "keys", "music", "musical", "piano", "social" ] -}, { - "name" : "add_road", - "tags" : [ "+", "add", "destination", "direction", "highway", "maps", "new", "plus", "road", "stop", "street", "symbol", "traffic" ] -}, { - "name" : "add_ic_call", - "tags" : [ "+", "add", "call", "cell", "contact", "device", "hardware", "mobile", "new", "phone", "plus", "symbol", "telephone" ] -}, { - "name" : "rule_folder", - "tags" : [ "approve", "cancel", "check", "close", "complete", "data", "doc", "document", "done", "drive", "exit", "file", "folder", "mark", "no", "ok", "remove", "rule", "select", "sheet", "slide", "storage", "tick", "validate", "verified", "x", "yes" ] -}, { - "name" : "switch_access_shortcut", - "tags" : [ "access", "arrow", "arrows", "direction", "navigation", "new", "north", "shortcut", "switch", "symbol", "up" ] -}, { - "name" : "hardware", - "tags" : [ "break", "construction", "hammer", "hardware", "nail", "repair", "tool" ] -}, { - "name" : "line_weight", - "tags" : [ "height", "line", "size", "spacing", "style", "thickness", "weight" ] -}, { - "name" : "image_not_supported", - "tags" : [ "disabled", "enabled", "image", "landscape", "mountain", "mountains", "not", "off", "on", "photo", "photography", "picture", "slash", "supported" ] -}, { - "name" : "flip_camera_ios", - "tags" : [ "DISABLE_IOS", "android", "camera", "disable_ios", "edit", "editing", "flip", "image", "ios", "mobile", "orientation", "rotate", "turn" ] -}, { - "name" : "phone_callback", - "tags" : [ "arrow", "call", "callback", "cell", "contact", "device", "down", "hardware", "mobile", "phone", "telephone" ] -}, { - "name" : "access_time_filled", - "tags" : [ ] -}, { - "name" : "dining", - "tags" : [ "cafe", "cafeteria", "cutlery", "diner", "dining", "eat", "eating", "fork", "room", "spoon" ] -}, { - "name" : "scale", - "tags" : [ "measure", "monitor", "scale", "weight" ] -}, { - "name" : "airplanemode_active", - "tags" : [ "active", "airplane", "airplanemode", "flight", "mode", "on", "signal" ] -}, { - "name" : "set_meal", - "tags" : [ "chopsticks", "dinner", "fish", "food", "lunch", "meal", "restaurant", "set", "teishoku" ] -}, { - "name" : "mobile_friendly", - "tags" : [ "Android", "OS", "approve", "cell", "check", "complete", "device", "done", "friendly", "hardware", "iOS", "mark", "mobile", "ok", "phone", "select", "tablet", "tick", "validate", "verified", "yes" ] -}, { - "name" : "assured_workload", - "tags" : [ "assured", "compliance", "confidential", "federal", "government", "secure", "sensitive regulatory", "workload" ] -}, { - "name" : "wallet", - "tags" : [ ] -}, { - "name" : "merge_type", - "tags" : [ "arrow", "combine", "direction", "format", "merge", "text", "type" ] -}, { - "name" : "view_timeline", - "tags" : [ "grid", "layout", "pattern", "squares", "timeline", "view" ] -}, { - "name" : "departure_board", - "tags" : [ "automobile", "board", "bus", "car", "cars", "clock", "departure", "maps", "public", "schedule", "time", "transportation", "travel", "vehicle" ] -}, { - "name" : "event_repeat", - "tags" : [ "around", "calendar", "date", "day", "event", "inprogress", "load", "loading refresh", "month", "renew", "rotate", "schedule", "turn" ] -}, { - "name" : "sanitizer", - "tags" : [ "bacteria", "bottle", "clean", "covid", "disinfect", "germs", "pump", "sanitizer" ] -}, { - "name" : "surfing", - "tags" : [ "athlete", "athletic", "beach", "body", "entertainment", "exercise", "hobby", "human", "people", "person", "sea", "social sports", "sports", "summer", "surfing", "water" ] -}, { - "name" : "pix", - "tags" : [ "bill", "brazil", "card", "cash", "commerce", "credit", "currency", "finance", "money", "payment" ] -}, { - "name" : "phonelink_ring", - "tags" : [ "Android", "OS", "cell", "connection", "data", "device", "hardware", "iOS", "mobile", "network", "phone", "phonelink", "ring", "service", "signal", "tablet", "wireless" ] -}, { - "name" : "display_settings", - "tags" : [ "Android", "OS", "application", "change", "chrome", "desktop", "details", "device", "display", "gear", "hardware", "iOS", "info", "information", "mac", "monitor", "options", "personal", "screen", "service", "settings", "web", "window" ] -}, { - "name" : "sports_motorsports", - "tags" : [ "athlete", "athletic", "automobile", "bike", "drive", "driving", "entertainment", "helmet", "hobby", "motorcycle", "motorsports", "protect", "social", "sports", "vehicle" ] -}, { - "name" : "horizontal_split", - "tags" : [ "bars", "format", "horizontal", "layout", "lines", "split", "stacked" ] -}, { - "name" : "view_comfy", - "tags" : [ "comfy", "grid", "layout", "pattern", "squares", "view" ] -}, { - "name" : "polymer", - "tags" : [ "emblem", "logo", "mark", "polymer" ] -}, { - "name" : "golf_course", - "tags" : [ "athlete", "athletic", "ball", "club", "course", "entertainment", "flag", "golf", "golfer", "golfing", "hobby", "hole", "places", "putt", "sports" ] -}, { - "name" : "batch_prediction", - "tags" : [ "batch", "bulb", "idea", "light", "prediction" ] -}, { - "name" : "filter_1", - "tags" : [ "1", "digit", "edit", "editing", "effect", "filter", "image", "images", "multiple", "number", "photography", "picture", "pictures", "settings", "stack", "symbol" ] -}, { - "name" : "stay_current_portrait", - "tags" : [ "Android", "OS", "current", "device", "hardware", "iOS", "mobile", "phone", "portrait", "stay", "tablet" ] -}, { - "name" : "usb", - "tags" : [ "cable", "connection", "device", "usb", "wire" ] -}, { - "name" : "featured_play_list", - "tags" : [ "collection", "featured", "highlighted", "list", "music", "play", "playlist", "recommended" ] -}, { - "name" : "data_object", - "tags" : [ "brackets", "code", "coder", "data", "object", "parentheses" ] -}, { - "name" : "co_present", - "tags" : [ "arrow", "co-present", "presentation", "screen", "share", "site", "slides", "togather", "web", "website" ] -}, { - "name" : "ev_station", - "tags" : [ "automobile", "car", "cars", "charging", "electric", "electricity", "ev", "maps", "places", "station", "transportation", "vehicle" ] -}, { - "name" : "send_and_archive", - "tags" : [ "archive", "arrow", "down", "download", "email", "letter", "mail", "save", "send", "share" ] -}, { - "name" : "send_to_mobile", - "tags" : [ "Android", "OS", "arrow", "device", "export", "forward", "hardware", "iOS", "mobile", "phone", "right", "send", "share", "tablet", "to" ] -}, { - "name" : "local_see", - "tags" : [ "camera", "lens", "local", "photo", "photography", "picture", "see" ] -}, { - "name" : "satellite_alt", - "tags" : [ "alternative", "artificial", "communication", "satellite", "space", "space station", "television" ] -}, { - "name" : "flatware", - "tags" : [ "cafe", "cafeteria", "cutlery", "diner", "dining", "eat", "eating", "fork", "room", "spoon" ] -}, { - "name" : "speaker", - "tags" : [ "box", "electronic", "loud", "music", "sound", "speaker", "stereo", "system", "video" ] -}, { - "name" : "adb", - "tags" : [ "adb", "android", "bridge", "debug" ] -}, { - "name" : "movie_creation", - "tags" : [ "cinema", "clapperboard", "creation", "film", "movie", "movies", "slate", "video" ] -}, { - "name" : "picture_in_picture", - "tags" : [ "crop", "cropped", "overlap", "photo", "picture", "position", "shape" ] -}, { - "name" : "call_received", - "tags" : [ "arrow", "call", "device", "mobile", "received" ] -}, { - "name" : "battery_alert", - "tags" : [ "!", "alert", "attention", "battery", "caution", "cell", "charge", "danger", "error", "exclamation", "important", "mark", "mobile", "notification", "power", "symbol", "warning" ] -}, { - "name" : "system_update", - "tags" : [ "Android", "OS", "arrow", "arrows", "cell", "device", "direction", "down", "download", "hardware", "iOS", "install", "mobile", "phone", "system", "tablet", "update" ] -}, { - "name" : "webhook", - "tags" : [ "api", "developer", "development", "enterprise", "software", "webhook" ] -}, { - "name" : "add_chart", - "tags" : [ "+", "add", "analytics", "bar", "bars", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "new", "plus", "statistics", "symbol", "tracking" ] -}, { - "name" : "pan_tool_alt", - "tags" : [ "fingers", "gesture", "hand", "hands", "human", "move", "pan", "scan", "stop", "tool" ] -}, { - "name" : "sports_handball", - "tags" : [ "athlete", "athletic", "ball", "body", "entertainment", "exercise", "game", "handball", "hobby", "human", "people", "person", "social", "sports" ] -}, { - "name" : "electric_car", - "tags" : [ "automobile", "car", "cars", "electric", "electricity", "maps", "transportation", "travel", "vehicle" ] -}, { - "name" : "phone_forwarded", - "tags" : [ "arrow", "call", "cell", "contact", "device", "direction", "forwarded", "hardware", "mobile", "phone", "right", "telephone" ] -}, { - "name" : "add_to_photos", - "tags" : [ "add", "collection", "image", "landscape", "mountain", "mountains", "photo", "photography", "photos", "picture", "plus", "to" ] -}, { - "name" : "power_off", - "tags" : [ "charge", "cord", "disabled", "electric", "electrical", "enabled", "off", "on", "outlet", "plug", "power", "slash" ] -}, { - "name" : "noise_control_off", - "tags" : [ "audio", "aware", "cancel", "cancellation", "control", "disabled", "enabled", "music", "noise", "note", "off", "offline", "on", "slash", "sound" ] -}, { - "name" : "code_off", - "tags" : [ "brackets", "code", "css", "develop", "developer", "disabled", "enabled", "engineer", "engineering", "html", "off", "on", "platform", "slash" ] -}, { - "name" : "bookmark_remove", - "tags" : [ "bookmark", "delete", "favorite", "minus", "remember", "remove", "ribbon", "save", "subtract" ] -}, { - "name" : "screen_search_desktop", - "tags" : [ "Android", "OS", "arrow", "desktop", "device", "hardware", "iOS", "lock", "monitor", "rotate", "screen", "web" ] -}, { - "name" : "panorama", - "tags" : [ "angle", "image", "mountain", "mountains", "panorama", "photo", "photography", "picture", "view", "wide" ] -}, { - "name" : "settings_bluetooth", - "tags" : [ "bluetooth", "connect", "connection", "connectivity", "device", "settings", "signal", "symbol" ] -}, { - "name" : "sports_baseball", - "tags" : [ "athlete", "athletic", "ball", "baseball", "entertainment", "exercise", "game", "hobby", "social", "sports" ] -}, { - "name" : "festival", - "tags" : [ "circus", "event", "festival", "local", "maps", "places", "tent", "tour", "travel" ] -}, { - "name" : "lens_blur", - "tags" : [ "blur", "camera", "dim", "dot", "effect", "foggy", "fuzzy", "image", "lens", "photo", "soften" ] -}, { - "name" : "plumbing", - "tags" : [ "build", "construction", "fix", "handyman", "plumbing", "repair", "tools", "wrench" ] -}, { - "name" : "toys", - "tags" : [ "car", "games", "kids", "toy", "toys", "windmill" ] -}, { - "name" : "coffee_maker", - "tags" : [ "appliances", "beverage", "coffee", "cup", "drink", "machine", "maker", "mug" ] -}, { - "name" : "edit_notifications", - "tags" : [ "active", "alarm", "alert", "bell", "chime", "compose", "create", "draft", "edit", "editing", "input", "new", "notifications", "notify", "pen", "pencil", "reminder", "ring", "sound", "write", "writing" ] -}, { - "name" : "personal_video", - "tags" : [ "Android", "OS", "cam", "chrome", "desktop", "device", "hardware", "iOS", "mac", "monitor", "personal", "television", "tv", "video", "web", "window" ] -}, { - "name" : "animation", - "tags" : [ "animation", "circles", "film", "motion", "movement", "sequence", "video" ] -}, { - "name" : "bedtime", - "tags" : [ "bedtime", "nightime", "sleep" ] -}, { - "name" : "gamepad", - "tags" : [ "buttons", "console", "controller", "device", "game", "gamepad", "gaming", "playstation", "video" ] -}, { - "name" : "diversity_1", - "tags" : [ "committee", "diverse", "diversity", "family", "friends", "group", "groups", "heart", "humans", "network", "people", "persons", "social", "team" ] -}, { - "name" : "center_focus_weak", - "tags" : [ "camera", "center", "focus", "image", "lens", "photo", "photography", "weak", "zoom" ] -}, { - "name" : "signal_wifi_statusbar_4_bar", - "tags" : [ "4", "bar", "cell", "cellular", "data", "internet", "mobile", "network", "phone", "signal", "speed", "statusbar", "wifi", "wireless" ] -}, { - "name" : "manage_history", - "tags" : [ "application", "arrow", "back", "backwards", "change", "clock", "date", "details", "gear", "history", "options", "refresh", "renew", "reverse", "rotate", "schedule", "settings", "time", "turn" ] -}, { - "name" : "folder_zip", - "tags" : [ "compress", "data", "doc", "document", "drive", "file", "folder", "folders", "open", "sheet", "slide", "storage", "zip" ] -}, { - "name" : "flag_circle", - "tags" : [ "circle", "country", "flag", "goal", "mark", "nation", "report", "round", "start" ] -}, { - "name" : "south_west", - "tags" : [ "arrow", "directional", "down", "left", "maps", "navigation", "south", "west" ] -}, { - "name" : "looks_4", - "tags" : [ "4", "digit", "looks", "numbers", "square", "symbol" ] -}, { - "name" : "cloud_circle", - "tags" : [ "app", "application", "backup", "circle", "cloud", "connection", "drive", "files", "folders", "internet", "network", "sky", "storage", "upload" ] -}, { - "name" : "format_shapes", - "tags" : [ "alphabet", "character", "color", "doc", "edit", "editing", "editor", "fill", "font", "format", "letter", "paint", "shapes", "sheet", "spreadsheet", "style", "symbol", "text", "type", "writing" ] -}, { - "name" : "car_rental", - "tags" : [ "automobile", "car", "cars", "key", "maps", "rental", "transportation", "vehicle" ] -}, { - "name" : "movie_filter", - "tags" : [ "ai", "artificial", "automatic", "automation", "clapperboard", "creation", "custom", "film", "filter", "genai", "intelligence", "magic", "movie", "movies", "slate", "smart", "spark", "sparkle", "star", "stars", "video" ] -}, { - "name" : "layers_clear", - "tags" : [ "arrange", "clear", "delete", "disabled", "enabled", "interaction", "layers", "maps", "off", "on", "overlay", "pages", "slash" ] -}, { - "name" : "phonelink_lock", - "tags" : [ "Android", "OS", "cell", "connection", "device", "erase", "hardware", "iOS", "lock", "locked", "mobile", "password", "phone", "phonelink", "privacy", "private", "protection", "safety", "secure", "security", "tablet" ] -}, { - "name" : "attractions", - "tags" : [ "amusement", "attractions", "entertainment", "ferris", "fun", "maps", "park", "places", "wheel" ] -}, { - "name" : "playlist_add_check_circle", - "tags" : [ "add", "album", "artist", "audio", "cd", "check", "circle", "collection", "list", "mark", "music", "playlist", "record", "sound", "track" ] -}, { - "name" : "hive", - "tags" : [ "bee", "honey", "honeycomb" ] -}, { - "name" : "no_photography", - "tags" : [ "camera", "disabled", "enabled", "image", "no", "off", "on", "photo", "photography", "picture", "slash" ] -}, { - "name" : "content_paste_go", - "tags" : [ "clipboard", "content", "disabled", "doc", "document", "enabled", "file", "go", "on", "paste", "slash" ] -}, { - "name" : "shop_two", - "tags" : [ "add", "arrow", "buy", "cart", "google", "play", "purchase", "shop", "shopping", "two" ] -}, { - "name" : "edit_location", - "tags" : [ "destination", "direction", "edit", "location", "maps", "pen", "pencil", "pin", "place", "stop" ] -}, { - "name" : "screen_rotation", - "tags" : [ "Android", "OS", "arrow", "device", "hardware", "iOS", "mobile", "phone", "rotate", "rotation", "screen", "tablet", "turn" ] -}, { - "name" : "numbers", - "tags" : [ "digit", "number", "numbers", "symbol" ] -}, { - "name" : "sim_card", - "tags" : [ "camera", "card", "chip", "device", "memory", "phone", "sim", "storage" ] -}, { - "name" : "control_camera", - "tags" : [ "adjust", "arrow", "arrows", "camera", "center", "control", "direction", "left", "move", "right" ] -}, { - "name" : "blender", - "tags" : [ "appliance", "blender", "cooking", "electric", "juicer", "kitchen", "machine", "vitamix" ] -}, { - "name" : "flip_to_front", - "tags" : [ "arrange", "arrangement", "back", "flip", "format", "front", "layout", "move", "order", "sort", "to" ] -}, { - "name" : "sports_volleyball", - "tags" : [ "athlete", "athletic", "ball", "entertainment", "exercise", "game", "hobby", "social", "sports", "volleyball" ] -}, { - "name" : "stairs", - "tags" : [ "down", "staircase", "stairs", "up" ] -}, { - "name" : "keyboard_alt", - "tags" : [ "alt", "computer", "device", "hardware", "input", "keyboard", "keypad", "letter", "office", "text", "type" ] -}, { - "name" : "crop_din", - "tags" : [ "adjust", "adjustments", "area", "crop", "din", "edit", "editing", "frame", "image", "images", "photo", "photos", "rectangle", "settings", "size", "square" ] -}, { - "name" : "html", - "tags" : [ "alphabet", "brackets", "character", "code", "css", "develop", "developer", "engineer", "engineering", "font", "html", "letter", "platform", "symbol", "text", "type" ] -}, { - "name" : "signal_wifi_statusbar_connected_no_internet_4", - "tags" : [ "!", "4", "alert", "attention", "caution", "cell", "cellular", "connected", "danger", "data", "error", "exclamation", "important", "internet", "mark", "mobile", "network", "no", "notification", "phone", "signal", "speed", "statusbar", "symbol", "warning", "wifi", "wireless" ] -}, { - "name" : "pivot_table_chart", - "tags" : [ "analytics", "arrow", "arrows", "bar", "bars", "chart", "data", "diagram", "direction", "drive", "edit", "editing", "graph", "grid", "infographic", "measure", "metrics", "pivot", "rotate", "sheet", "statistics", "table", "tracking" ] -}, { - "name" : "microwave", - "tags" : [ "appliance", "cooking", "electric", "heat", "home", "house", "kitchen", "machine", "microwave" ] -}, { - "name" : "folder_copy", - "tags" : [ "content", "copy", "cut", "data", "doc", "document", "drive", "duplicate", "file", "folder", "folders", "multiple", "paste", "sheet", "slide", "storage" ] -}, { - "name" : "output", - "tags" : [ ] -}, { - "name" : "gif_box", - "tags" : [ "alphabet", "animated", "animation", "bitmap", "character", "font", "format", "gif", "graphics", "interchange", "letter", "symbol", "text", "type" ] -}, { - "name" : "voice_chat", - "tags" : [ "bubble", "cam", "camera", "chat", "comment", "communicate", "facetime", "feedback", "message", "speech", "video", "voice" ] -}, { - "name" : "local_convenience_store", - "tags" : [ "--", "24", "bill", "building", "business", "card", "cash", "coin", "commerce", "company", "convenience", "credit", "currency", "dollars", "local", "maps", "market", "money", "new", "online", "pay", "payment", "plus", "shop", "shopping", "store", "storefront", "symbol" ] -}, { - "name" : "gps_not_fixed", - "tags" : [ "destination", "direction", "disabled", "enabled", "gps", "location", "maps", "not fixed", "off", "on", "online", "place", "pointer", "slash", "tracking" ] -}, { - "name" : "high_quality", - "tags" : [ "alphabet", "character", "definition", "display", "font", "high", "hq", "letter", "movie", "movies", "quality", "resolution", "screen", "symbol", "text", "tv", "type" ] -}, { - "name" : "switch_right", - "tags" : [ "arrows", "directional", "navigation", "right", "switch", "toggle" ] -}, { - "name" : "pages", - "tags" : [ "article", "gplus", "pages", "paper", "post", "star" ] -}, { - "name" : "table_restaurant", - "tags" : [ "bar", "dining", "table" ] -}, { - "name" : "speaker_notes_off", - "tags" : [ "bubble", "chat", "comment", "communicate", "disabled", "enabled", "format", "list", "message", "notes", "off", "on", "slash", "speaker", "speech", "text" ] -}, { - "name" : "phone_disabled", - "tags" : [ "call", "cell", "contact", "device", "disabled", "enabled", "hardware", "mobile", "off", "offline", "on", "phone", "slash", "telephone" ] -}, { - "name" : "eject", - "tags" : [ "disc", "drive", "dvd", "eject", "remove", "triangle", "usb" ] -}, { - "name" : "control_point_duplicate", - "tags" : [ "+", "add", "circle", "control", "duplicate", "multiple", "new", "plus", "point", "symbol" ] -}, { - "name" : "filter", - "tags" : [ "edit", "editing", "effect", "filter", "image", "landscape", "mountain", "mountains", "photo", "photography", "picture", "settings" ] -}, { - "name" : "pest_control", - "tags" : [ "bug", "control", "exterminator", "insects", "pest" ] -}, { - "name" : "backpack", - "tags" : [ "back", "backpack", "bag", "book", "bookbag", "knapsack", "pack", "storage", "travel" ] -}, { - "name" : "leak_add", - "tags" : [ "add", "connection", "data", "leak", "link", "network", "service", "signals", "synce", "wireless" ] -}, { - "name" : "zoom_in_map", - "tags" : [ "arrow", "arrows", "destination", "in", "location", "maps", "move", "place", "stop", "zoom" ] -}, { - "name" : "brightness_7", - "tags" : [ "7", "brightness", "circle", "control", "crescent", "level", "moon", "screen", "sun" ] -}, { - "name" : "system_security_update_good", - "tags" : [ "Android", "OS", "approve", "cell", "check", "complete", "device", "done", "good", "hardware", "iOS", "mark", "mobile", "ok", "phone", "security", "select", "system", "tablet", "tick", "update", "validate", "verified", "yes" ] -}, { - "name" : "ring_volume", - "tags" : [ "call", "calling", "cell", "contact", "device", "hardware", "incoming", "mobile", "phone", "ring", "ringer", "sound", "telephone", "volume" ] -}, { - "name" : "money_off_csred", - "tags" : [ "bill", "card", "cart", "cash", "coin", "commerce", "credit", "csred", "currency", "disabled", "dollars", "enabled", "money", "off", "on", "online", "pay", "payment", "shopping", "slash", "symbol" ] -}, { - "name" : "sports_football", - "tags" : [ "athlete", "athletic", "ball", "entertainment", "exercise", "football", "game", "hobby", "social", "sports" ] -}, { - "name" : "nature", - "tags" : [ "forest", "nature", "outdoor", "outside", "park", "tree", "wilderness" ] -}, { - "name" : "vibration", - "tags" : [ "Android", "OS", "alert", "cell", "device", "hardware", "iOS", "mobile", "mode", "motion", "notification", "phone", "silence", "silent", "tablet", "vibrate", "vibration" ] -}, { - "name" : "snippet_folder", - "tags" : [ "data", "doc", "document", "drive", "file", "folder", "sheet", "slide", "snippet", "storage" ] -}, { - "name" : "edit_road", - "tags" : [ "destination", "direction", "edit", "highway", "maps", "pen", "pencil", "road", "street", "traffic" ] -}, { - "name" : "run_circle", - "tags" : [ "body", "circle", "exercise", "human", "people", "person", "run", "running" ] -}, { - "name" : "dry_cleaning", - "tags" : [ "cleaning", "dry", "hanger", "hotel", "laundry", "places", "service", "towel" ] -}, { - "name" : "alarm_off", - "tags" : [ "alarm", "alert", "bell", "clock", "disabled", "duration", "enabled", "notification", "off", "on", "slash", "time", "timer", "watch" ] -}, { - "name" : "perm_data_setting", - "tags" : [ "data", "gear", "info", "information", "perm", "settings" ] -}, { - "name" : "bedroom_parent", - "tags" : [ "bed", "bedroom", "double", "full", "furniture", "home", "hotel", "house", "king", "night", "parent", "pillows", "queen", "rest", "room", "sizem master", "sleep" ] -}, { - "name" : "airline_seat_recline_normal", - "tags" : [ "airline", "body", "extra", "feet", "human", "leg", "legroom", "normal", "people", "person", "recline", "seat", "sitting", "space", "travel" ] -}, { - "name" : "currency_bitcoin", - "tags" : [ "bill", "blockchain", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "digital", "dollars", "finance", "franc", "money", "online", "pay", "payment", "price", "shopping", "symbol" ] -}, { - "name" : "do_disturb_alt", - "tags" : [ "cancel", "close", "denied", "deny", "disturb", "do", "remove", "silence", "stop" ] -}, { - "name" : "sensor_window", - "tags" : [ "alarm", "security", "security system" ] -}, { - "name" : "incomplete_circle", - "tags" : [ "chart", "circle", "incomplete" ] -}, { - "name" : "settings_input_hdmi", - "tags" : [ "cable", "connection", "connectivity", "definition", "hdmi", "high", "input", "plug", "plugin", "points", "settings", "video", "wire" ] -}, { - "name" : "camera_indoor", - "tags" : [ "architecture", "building", "camera", "estate", "film", "filming", "home", "house", "image", "indoor", "inside", "motion", "nest", "picture", "place", "real", "residence", "residential", "shelter", "video", "videography" ] -}, { - "name" : "edit_location_alt", - "tags" : [ "alt", "edit", "location", "pen", "pencil", "pin" ] -}, { - "name" : "texture", - "tags" : [ "diagonal", "lines", "pattern", "stripes", "texture" ] -}, { - "name" : "location_off", - "tags" : [ "destination", "direction", "location", "maps", "off", "pin", "place", "room", "stop" ] -}, { - "name" : "edit_attributes", - "tags" : [ "approve", "attribution", "check", "complete", "done", "edit", "mark", "ok", "select", "tick", "validate", "verified", "yes" ] -}, { - "name" : "duo", - "tags" : [ "call", "chat", "conference", "device", "duo", "video" ] -}, { - "name" : "slow_motion_video", - "tags" : [ "arrow", "control", "controls", "motion", "music", "play", "slow", "speed", "video" ] -}, { - "name" : "perm_scan_wifi", - "tags" : [ "alert", "announcement", "connection", "info", "information", "internet", "network", "perm", "scan", "service", "signal", "wifi", "wireless" ] -}, { - "name" : "phonelink_setup", - "tags" : [ "Android", "OS", "call", "chat", "device", "hardware", "iOS", "info", "mobile", "phone", "phonelink", "settings", "setup", "tablet", "text" ] -}, { - "name" : "hourglass_disabled", - "tags" : [ "clock", "countdown", "disabled", "empty", "enabled", "hourglass", "loading", "minute", "minutes", "off", "on", "slash", "time", "wait", "waiting" ] -}, { - "name" : "add_to_queue", - "tags" : [ "+", "Android", "OS", "add", "chrome", "desktop", "device", "display", "hardware", "iOS", "mac", "monitor", "new", "plus", "queue", "screen", "symbol", "to", "web", "window" ] -}, { - "name" : "pie_chart_outline", - "tags" : [ "analytics", "bar", "bars", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "outline", "pie", "statistics", "tracking" ] -}, { - "name" : "playlist_remove", - "tags" : [ "-", "collection", "list", "minus", "music", "playlist", "remove" ] -}, { - "name" : "next_week", - "tags" : [ "arrow", "bag", "baggage", "briefcase", "business", "case", "next", "suitcase", "week" ] -}, { - "name" : "church", - "tags" : [ "christian", "christianity", "religion", "spiritual", "worship" ] -}, { - "name" : "medical_information", - "tags" : [ "badge", "card", "health", "id", "information", "medical", "services" ] -}, { - "name" : "view_compact", - "tags" : [ "compact", "grid", "layout", "pattern", "squares", "view" ] -}, { - "name" : "timer_off", - "tags" : [ "alarm", "alert", "bell", "clock", "disabled", "duration", "enabled", "notification", "off", "on", "slash", "stop", "time", "timer", "watch" ] -}, { - "name" : "bluetooth_connected", - "tags" : [ "bluetooth", "cast", "connect", "connection", "device", "paring", "streaming", "symbol", "wireless" ] -}, { - "name" : "photo_size_select_actual", - "tags" : [ "actual", "image", "mountain", "mountains", "photo", "photography", "picture", "select", "size" ] -}, { - "name" : "short_text", - "tags" : [ "brief", "comment", "doc", "document", "note", "short", "text", "write", "writing" ] -}, { - "name" : "bedroom_baby", - "tags" : [ "babies", "baby", "bedroom", "child", "children", "home", "horse", "house", "infant", "kid", "newborn", "rocking", "room", "toddler", "young" ] -}, { - "name" : "video_camera_back", - "tags" : [ "back", "camera", "image", "landscape", "mountain", "mountains", "photo", "photography", "picture", "rear", "video" ] -}, { - "name" : "bathroom", - "tags" : [ "bath", "bathroom", "closet", "home", "house", "place", "plumbing", "room", "shower", "sprinkler", "wash", "water", "wc" ] -}, { - "name" : "downhill_skiing", - "tags" : [ "athlete", "athletic", "body", "downhill", "entertainment", "exercise", "hobby", "human", "people", "person", "ski social", "skiing", "snow", "sports", "travel", "winter" ] -}, { - "name" : "filter_list_off", - "tags" : [ "alt", "disabled", "edit", "filter", "list", "off", "offline", "options", "refine", "sift", "slash" ] -}, { - "name" : "connected_tv", - "tags" : [ "Android", "OS", "airplay", "chrome", "connect", "connected", "desktop", "device", "display", "hardware", "iOS", "mac", "monitor", "screen", "screencast", "streaming", "television", "tv", "web", "window", "wireless" ] -}, { - "name" : "format_indent_increase", - "tags" : [ "align", "alignment", "doc", "edit", "editing", "editor", "format", "increase", "indent", "indentation", "paragraph", "sheet", "spreadsheet", "text", "type", "writing" ] -}, { - "name" : "settings_cell", - "tags" : [ "Android", "OS", "cell", "device", "hardware", "iOS", "mobile", "phone", "settings", "tablet" ] -}, { - "name" : "remember_me", - "tags" : [ "Android", "OS", "avatar", "device", "hardware", "human", "iOS", "identity", "me", "mobile", "people", "person", "phone", "profile", "remember", "tablet", "user" ] -}, { - "name" : "kayaking", - "tags" : [ "athlete", "athletic", "body", "canoe", "entertainment", "exercise", "hobby", "human", "kayak", "kayaking", "lake", "paddle", "paddling", "people", "person", "rafting", "river", "row", "social", "sports", "summer", "travel", "water" ] -}, { - "name" : "switch_access_shortcut_add", - "tags" : [ "+", "access", "add", "arrow", "arrows", "direction", "navigation", "new", "north", "plus", "shortcut", "switch", "symbol", "up" ] -}, { - "name" : "app_blocking", - "tags" : [ "Android", "OS", "app", "application", "block", "blocking", "cancel", "cell", "device", "hardware", "iOS", "mobile", "phone", "stop", "stopped", "tablet" ] -}, { - "name" : "elevator", - "tags" : [ "body", "down", "elevator", "human", "people", "person", "up" ] -}, { - "name" : "work_off", - "tags" : [ "bag", "baggage", "briefcase", "business", "case", "disabled", "enabled", "job", "off", "on", "slash", "suitcase", "work" ] -}, { - "name" : "sensors_off", - "tags" : [ "connection", "disabled", "enabled", "network", "off", "on", "scan", "sensors", "signal", "slash", "wireless" ] -}, { - "name" : "stay_primary_portrait", - "tags" : [ "Android", "OS", "current", "device", "hardware", "iOS", "mobile", "phone", "portrait", "primary", "stay", "tablet" ] -}, { - "name" : "cell_tower", - "tags" : [ "broadcast", "casting", "cell", "network", "signal", "tower", "transmitting", "wireless" ] -}, { - "name" : "moped", - "tags" : [ "automobile", "bike", "car", "cars", "maps", "scooter", "transportation", "vehicle", "vespa" ] -}, { - "name" : "wrong_location", - "tags" : [ "cancel", "close", "destination", "direction", "exit", "location", "maps", "no", "pin", "place", "quit", "remove", "stop", "wrong", "x" ] -}, { - "name" : "groups_2", - "tags" : [ "body", "club", "collaboration", "crowd", "gathering", "groups", "hair", "human", "meeting", "people", "person", "social", "teams" ] -}, { - "name" : "public_off", - "tags" : [ "disabled", "earth", "enabled", "global", "globe", "map", "network", "off", "on", "planet", "public", "slash", "social", "space", "web", "world" ] -}, { - "name" : "picture_in_picture_alt", - "tags" : [ "crop", "cropped", "overlap", "photo", "picture", "position", "shape" ] -}, { - "name" : "chair_alt", - "tags" : [ "cahir", "furniture", "home", "house", "kitchen", "lounging", "seating", "table" ] -}, { - "name" : "car_repair", - "tags" : [ "automobile", "car", "cars", "maps", "repair", "transportation", "vehicle" ] -}, { - "name" : "airplay", - "tags" : [ "airplay", "arrow", "connect", "control", "desktop", "device", "display", "monitor", "screen", "signal" ] -}, { - "name" : "nfc", - "tags" : [ "communication", "data", "field", "mobile", "near", "nfc", "wireless" ] -}, { - "name" : "line_style", - "tags" : [ "dash", "dotted", "line", "rule", "spacing", "style" ] -}, { - "name" : "transform", - "tags" : [ "adjust", "crop", "edit", "editing", "image", "photo", "picture", "transform" ] -}, { - "name" : "single_bed", - "tags" : [ "bed", "bedroom", "double", "furniture", "home", "hotel", "house", "king", "night", "pillows", "queen", "rest", "room", "single", "sleep", "twin" ] -}, { - "name" : "pattern", - "tags" : [ "key", "login", "password", "pattern", "pin", "security", "star", "unlock" ] -}, { - "name" : "local_movies", - "tags" : [ ] -}, { - "name" : "repeat_one", - "tags" : [ "1", "arrow", "arrows", "control", "controls", "digit", "media", "music", "number", "one", "repeat", "symbol", "video" ] -}, { - "name" : "swap_calls", - "tags" : [ "arrow", "arrows", "calls", "device", "direction", "mobile", "share", "swap" ] -}, { - "name" : "do_not_disturb_alt", - "tags" : [ "cancel", "close", "denied", "deny", "disturb", "do", "remove", "silence", "stop" ] -}, { - "name" : "smoking_rooms", - "tags" : [ "allowed", "cigarette", "places", "rooms", "smoke", "smoking", "tobacco", "zone" ] -}, { - "name" : "remove_moderator", - "tags" : [ "certified", "disabled", "enabled", "moderator", "off", "on", "privacy", "private", "protect", "protection", "remove", "security", "shield", "slash", "verified" ] -}, { - "name" : "perm_device_information", - "tags" : [ "Android", "OS", "alert", "announcement", "device", "hardware", "i", "iOS", "info", "information", "mobile", "perm", "phone", "tablet" ] -}, { - "name" : "wash", - "tags" : [ "bathroom", "clean", "fingers", "gesture", "hand", "wash", "wc" ] -}, { - "name" : "mode_standby", - "tags" : [ "disturb", "mode", "power", "sleep", "standby", "target" ] -}, { - "name" : "door_sliding", - "tags" : [ "auto", "automatic", "door", "doorway", "double", "entrance", "exit", "glass", "home", "house", "sliding", "two" ] -}, { - "name" : "skateboarding", - "tags" : [ "athlete", "athletic", "body", "entertainment", "exercise", "hobby", "human", "people", "person", "skate", "skateboarder", "skateboarding", "social", "sports" ] -}, { - "name" : "difference", - "tags" : [ "compare", "content", "copy", "cut", "diff", "difference", "doc", "document", "duplicate", "file", "multiple", "past" ] -}, { - "name" : "group_remove", - "tags" : [ "accounts", "committee", "face", "family", "friends", "group", "humans", "network", "people", "persons", "profiles", "remove", "social", "team", "users" ] -}, { - "name" : "brightness_high", - "tags" : [ "auto", "brightness", "control", "high", "mobile", "monitor", "phone", "sun" ] -}, { - "name" : "cabin", - "tags" : [ "architecture", "cabin", "camping", "cottage", "estate", "home", "house", "log", "maps", "place", "real", "residence", "residential", "stay", "traveling", "wood" ] -}, { - "name" : "camera_outdoor", - "tags" : [ "architecture", "building", "camera", "estate", "film", "filming", "home", "house", "image", "motion", "nest", "outdoor", "outside", "picture", "place", "real", "residence", "residential", "shelter", "video", "videography" ] -}, { - "name" : "troubleshoot", - "tags" : [ "analytics", "chart", "data", "diagram", "find", "glass", "graph", "infographic", "line", "look", "magnify", "magnifying", "measure", "metrics", "search", "see", "statistics", "tracking", "troubleshoot" ] -}, { - "name" : "tablet_android", - "tags" : [ "OS", "android", "device", "hardware", "iOS", "ipad", "mobile", "tablet", "web" ] -}, { - "name" : "house_siding", - "tags" : [ "architecture", "building", "construction", "estate", "exterior", "facade", "home", "house", "real", "residential", "siding" ] -}, { - "name" : "satellite", - "tags" : [ "bluetooth", "connect", "connection", "connectivity", "data", "device", "image", "internet", "landscape", "location", "maps", "mountain", "mountains", "network", "photo", "photography", "picture", "satellite", "scan", "service", "signal", "symbol", "wireless-- wifi" ] -}, { - "name" : "motion_photos_on", - "tags" : [ "animation", "circle", "disabled", "enabled", "motion", "off", "on", "photos", "play", "slash", "video" ] -}, { - "name" : "door_back", - "tags" : [ "back", "closed", "door", "doorway", "entrance", "exit", "home", "house", "way" ] -}, { - "name" : "strikethrough_s", - "tags" : [ "alphabet", "character", "cross", "doc", "edit", "editing", "editor", "font", "letter", "out", "s", "sheet", "spreadsheet", "strikethrough", "styles", "symbol", "text", "type", "writing" ] -}, { - "name" : "co2", - "tags" : [ "carbon", "chemical", "co2", "dioxide", "gas" ] -}, { - "name" : "notifications_paused", - "tags" : [ "active", "alarm", "alert", "bell", "chime", "ignore", "notifications", "notify", "paused", "quiet", "reminder", "ring --- pause", "sleep", "snooze", "sound", "z", "zzz" ] -}, { - "name" : "currency_yen", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "money", "online", "pay", "payment", "price", "shopping", "symbol", "yen" ] -}, { - "name" : "call_to_action", - "tags" : [ "action", "alert", "bar", "call", "components", "cta", "design", "info", "information", "interface", "layout", "message", "notification", "screen", "site", "to", "ui", "ux", "web", "website", "window" ] -}, { - "name" : "photo_camera_front", - "tags" : [ "account", "camera", "face", "front", "human", "image", "people", "person", "photo", "photography", "picture", "portrait", "profile", "user" ] -}, { - "name" : "directions_boat_filled", - "tags" : [ "automobile", "boat", "car", "cars", "direction", "directions", "ferry", "filled", "maps", "public", "transportation", "vehicle" ] -}, { - "name" : "subtitles_off", - "tags" : [ "accessibility", "accessible", "caption", "cc", "closed", "disabled", "enabled", "language", "off", "on", "slash", "subtitle", "subtitles", "translate", "video" ] -}, { - "name" : "rotate_90_degrees_ccw", - "tags" : [ "90", "arrow", "arrows", "ccw", "degrees", "direction", "edit", "editing", "image", "photo", "rotate", "turn" ] -}, { - "name" : "vertical_align_center", - "tags" : [ "align", "alignment", "arrow", "center", "doc", "down", "edit", "editing", "editor", "sheet", "spreadsheet", "text", "type", "up", "vertical", "writing" ] -}, { - "name" : "living", - "tags" : [ "chair", "comfort", "couch", "decoration", "furniture", "home", "house", "living", "lounging", "loveseat", "room", "seat", "seating", "sofa" ] -}, { - "name" : "battery_saver", - "tags" : [ "+", "add", "battery", "charge", "charging", "new", "plus", "power", "saver", "symbol" ] -}, { - "name" : "hot_tub", - "tags" : [ "bath", "bathing", "bathroom", "bathtub", "hot", "hotel", "human", "jacuzzi", "person", "shower", "spa", "steam", "travel", "tub", "water" ] -}, { - "name" : "play_lesson", - "tags" : [ "audio", "book", "bookmark", "digital", "ebook", "lesson", "multimedia", "play", "play lesson", "read", "reading", "ribbon" ] -}, { - "name" : "update_disabled", - "tags" : [ "arrow", "back", "backwards", "clock", "date", "disabled", "enabled", "forward", "history", "load", "off", "on", "refresh", "reverse", "rotate", "schedule", "slash", "time", "update" ] -}, { - "name" : "psychology_alt", - "tags" : [ "?", "assistance", "behavior", "body", "brain", "cognitive", "function", "gear", "head", "help", "human", "info", "information", "intellectual", "mental", "mind", "people", "person", "preferences", "psychiatric", "psychology", "punctuation", "question mark", "science", "settings", "social", "support", "symbol", "therapy", "thinking", "thoughts" ] -}, { - "name" : "cast_connected", - "tags" : [ "Android", "OS", "airplay", "cast", "chrome", "connect", "connected", "desktop", "device", "display", "hardware", "iOS", "mac", "monitor", "screen", "screencast", "streaming", "television", "tv", "web", "window", "wireless" ] -}, { - "name" : "format_color_reset", - "tags" : [ "clear", "color", "disabled", "doc", "droplet", "edit", "editing", "editor", "enabled", "fill", "format", "off", "on", "paint", "reset", "sheet", "slash", "spreadsheet", "style", "text", "type", "water", "writing" ] -}, { - "name" : "snooze", - "tags" : [ "alarm", "bell", "clock", "duration", "notification", "snooze", "time", "timer", "watch", "z" ] -}, { - "name" : "person_remove_alt_1", - "tags" : [ ] -}, { - "name" : "align_horizontal_left", - "tags" : [ "align", "alignment", "format", "horizontal", "layout", "left", "lines", "paragraph", "rule", "rules", "style", "text" ] -}, { - "name" : "boy", - "tags" : [ "body", "boy", "gender", "human", "male", "man", "people", "person", "social", "symbol" ] -}, { - "name" : "battery_5_bar", - "tags" : [ "5", "bar", "battery", "cell", "charge", "mobile", "power" ] -}, { - "name" : "mic_external_on", - "tags" : [ "audio", "disabled", "enabled", "external", "mic", "microphone", "off", "on", "slash", "sound", "voice" ] -}, { - "name" : "voicemail", - "tags" : [ "call", "device", "message", "missed", "mobile", "phone", "recording", "voice", "voicemail" ] -}, { - "name" : "join_full", - "tags" : [ "circle", "combine", "command", "full", "join", "left", "outer", "overlap", "right", "sql" ] -}, { - "name" : "looks_5", - "tags" : [ "5", "digit", "looks", "numbers", "square", "symbol" ] -}, { - "name" : "countertops", - "tags" : [ "counter", "countertops", "home", "house", "kitchen", "sink", "table", "tops" ] -}, { - "name" : "energy_savings_leaf", - "tags" : [ "eco", "energy", "leaf", "leaves", "nest", "savings", "usage" ] -}, { - "name" : "safety_divider", - "tags" : [ "apart", "distance", "divider", "safety", "separate", "social", "space" ] -}, { - "name" : "move_up", - "tags" : [ "arrow", "direction", "jump", "move", "navigation", "transfer", "up" ] -}, { - "name" : "storm", - "tags" : [ "forecast", "hurricane", "storm", "temperature", "twister", "weather", "wind" ] -}, { - "name" : "sync_disabled", - "tags" : [ "360", "around", "arrow", "arrows", "direction", "disabled", "enabled", "inprogress", "load", "loading refresh", "off", "on", "renew", "rotate", "slash", "sync", "turn" ] -}, { - "name" : "javascript", - "tags" : [ "alphabet", "brackets", "character", "code", "css", "develop", "developer", "engineer", "engineering", "font", "html", "javascript", "letter", "platform", "symbol", "text", "type" ] -}, { - "name" : "tram", - "tags" : [ "automobile", "car", "cars", "direction", "maps", "public", "rail", "subway", "train", "tram", "transportation", "vehicle" ] -}, { - "name" : "app_shortcut", - "tags" : [ "app", "bookmarked", "favorite", "highlight", "important", "marked", "mobile", "save", "saved", "shortcut", "software", "special", "star" ] -}, { - "name" : "data_saver_off", - "tags" : [ "analytics", "bar", "bars", "chart", "data", "diagram", "donut", "graph", "infographic", "measure", "metrics", "off", "on", "ring", "saver", "statistics", "tracking" ] -}, { - "name" : "laptop_windows", - "tags" : [ "Android", "OS", "chrome", "device", "display", "hardware", "iOS", "laptop", "mac", "monitor", "screen", "web", "window", "windows" ] -}, { - "name" : "doorbell", - "tags" : [ "alarm", "bell", "door", "doorbell", "home", "house", "ringing" ] -}, { - "name" : "hd", - "tags" : [ "alphabet", "character", "definition", "display", "font", "hd", "high", "letter", "movie", "movies", "resolution", "screen", "symbol", "text", "tv", "type" ] -}, { - "name" : "file_download_off", - "tags" : [ "arrow", "disabled", "down", "download", "drive", "enabled", "export", "file", "install", "off", "on", "save", "slash", "upload" ] -}, { - "name" : "apps_outage", - "tags" : [ "all", "applications", "apps", "circles", "collection", "components", "dots", "grid", "interface", "outage", "squares", "ui", "ux" ] -}, { - "name" : "taxi_alert", - "tags" : [ "!", "alert", "attention", "automobile", "cab", "car", "cars", "caution", "danger", "direction", "error", "exclamation", "important", "lyft", "maps", "mark", "notification", "public", "symbol", "taxi", "transportation", "uber", "vehicle", "warning", "yellow" ] -}, { - "name" : "breakfast_dining", - "tags" : [ "bakery", "bread", "breakfast", "butter", "dining", "food", "toast" ] -}, { - "name" : "brightness_medium", - "tags" : [ "auto", "brightness", "control", "medium", "mobile", "monitor", "phone", "sun" ] -}, { - "name" : "gradient", - "tags" : [ "color", "edit", "editing", "effect", "filter", "gradient", "image", "images", "photography", "picture", "pictures" ] -}, { - "name" : "swipe_left", - "tags" : [ "arrow", "arrows", "finger", "hand", "hit", "left", "navigation", "reject", "strike", "swing", "swipe", "take" ] -}, { - "name" : "soup_kitchen", - "tags" : [ "breakfast", "brunch", "dining", "food", "kitchen", "lunch", "meal", "soup" ] -}, { - "name" : "voice_over_off", - "tags" : [ "account", "disabled", "enabled", "face", "human", "off", "on", "over", "people", "person", "profile", "recording", "slash", "speak", "speaking", "speech", "transcript", "user", "voice" ] -}, { - "name" : "water_damage", - "tags" : [ "architecture", "building", "damage", "drop", "droplet", "estate", "house", "leak", "plumbing", "real", "residence", "residential", "shelter", "water" ] -}, { - "name" : "abc", - "tags" : [ "alphabet", "character", "font", "letter", "symbol", "text", "type" ] -}, { - "name" : "data_saver_on", - "tags" : [ "+", "add", "analytics", "chart", "data", "diagram", "graph", "infographic", "measure", "metrics", "new", "on", "plus", "ring", "saver", "statistics", "symbol", "tracking" ] -}, { - "name" : "signal_wifi_0_bar", - "tags" : [ "0", "bar", "cell", "cellular", "data", "internet", "mobile", "network", "phone", "signal", "wifi", "wireless" ] -}, { - "name" : "brightness_low", - "tags" : [ "auto", "brightness", "control", "low", "mobile", "monitor", "phone", "sun" ] -}, { - "name" : "device_unknown", - "tags" : [ "?", "Android", "OS", "assistance", "cell", "device", "hardware", "help", "iOS", "info", "information", "mobile", "phone", "punctuation", "question mark", "support", "symbol", "tablet", "unknown" ] -}, { - "name" : "fire_extinguisher", - "tags" : [ "emergency", "extinguisher", "fire", "water" ] -}, { - "name" : "fitbit", - "tags" : [ "athlete", "athletic", "exercise", "fitbit", "fitness", "hobby", "logo" ] -}, { - "name" : "bedroom_child", - "tags" : [ "bed", "bedroom", "child", "children", "furniture", "home", "hotel", "house", "kid", "night", "pillows", "rest", "room", "size", "sleep", "twin", "young" ] -}, { - "name" : "closed_caption_off", - "tags" : [ "accessible", "alphabet", "caption", "cc", "character", "closed", "decoder", "font", "language", "letter", "media", "movies", "off", "outline", "subtitle", "subtitles", "symbol", "text", "tv", "type" ] -}, { - "name" : "bluetooth_searching", - "tags" : [ "bluetooth", "connection", "device", "paring", "search", "searching", "symbol" ] -}, { - "name" : "content_paste_off", - "tags" : [ "clipboard", "content", "disabled", "doc", "document", "enabled", "file", "off", "on", "paste", "slash" ] -}, { - "name" : "hexagon", - "tags" : [ "hexagon", "shape", "six sides" ] -}, { - "name" : "tap_and_play", - "tags" : [ "Android", "OS wifi", "cell", "connection", "device", "hardware", "iOS", "internet", "mobile", "network", "phone", "play", "signal", "tablet", "tap", "to", "wireless" ] -}, { - "name" : "domain_add", - "tags" : [ "+", "add", "apartment", "architecture", "building", "business", "domain", "estate", "home", "new", "place", "plus", "real", "residence", "residential", "shelter", "symbol", "web", "www" ] -}, { - "name" : "signpost", - "tags" : [ "arrow", "direction", "left", "maps", "right", "signal", "signs", "street", "traffic" ] -}, { - "name" : "screenshot", - "tags" : [ "Android", "OS", "cell", "crop", "device", "hardware", "iOS", "mobile", "phone", "screen", "screenshot", "tablet" ] -}, { - "name" : "network_cell", - "tags" : [ "cell", "cellular", "data", "internet", "mobile", "network", "phone", "speed", "wifi", "wireless" ] -}, { - "name" : "repeat_on", - "tags" : [ "arrow", "arrows", "control", "controls", "media", "music", "on", "repeat", "video" ] -}, { - "name" : "charging_station", - "tags" : [ "Android", "OS", "battery", "bolt", "cell", "charging", "device", "electric", "hardware", "iOS", "lightning", "mobile", "phone", "station", "tablet", "thunderbolt" ] -}, { - "name" : "grid_4x4", - "tags" : [ "4", "by", "grid", "layout", "lines", "space" ] -}, { - "name" : "assistant_photo", - "tags" : [ "assistant", "flag", "photo", "recommendation", "smart", "star", "suggestion" ] -}, { - "name" : "carpenter", - "tags" : [ "building", "carpenter", "construction", "cutting", "handyman", "repair", "saw", "tool" ] -}, { - "name" : "private_connectivity", - "tags" : [ "connectivity", "lock", "locked", "password", "privacy", "private", "protection", "safety", "secure", "security" ] -}, { - "name" : "mobiledata_off", - "tags" : [ "arrow", "data", "disabled", "down", "enabled", "internet", "mobile", "network", "off", "on", "slash", "speed", "up", "wifi", "wireless" ] -}, { - "name" : "atm", - "tags" : [ "alphabet", "atm", "automated", "bill", "card", "cart", "cash", "character", "coin", "commerce", "credit", "currency", "dollars", "font", "letter", "machine", "money", "online", "pay", "payment", "shopping", "symbol", "teller", "text", "type" ] -}, { - "name" : "rv_hookup", - "tags" : [ "arrow", "attach", "automobile", "automotive", "back", "car", "cars", "connect", "direction", "hookup", "left", "maps", "public", "right", "rv", "trailer", "transportation", "travel", "truck", "van", "vehicle" ] -}, { - "name" : "replay_30", - "tags" : [ "30", "arrow", "arrows", "control", "controls", "digit", "music", "number", "refresh", "renew", "repeat", "replay", "symbol", "thirty", "video" ] -}, { - "name" : "offline_share", - "tags" : [ "Android", "OS", "arrow", "cell", "connect", "device", "direction", "hardware", "iOS", "link", "mobile", "multiple", "offline", "phone", "right", "share", "tablet" ] -}, { - "name" : "settings_input_svideo", - "tags" : [ "cable", "connection", "connectivity", "definition", "input", "plug", "plugin", "points", "settings", "standard", "svideo", "video" ] -}, { - "name" : "soap", - "tags" : [ "bathroom", "clean", "fingers", "gesture", "hand", "soap", "wash", "wc" ] -}, { - "name" : "baby_changing_station", - "tags" : [ "babies", "baby", "bathroom", "body", "changing", "child", "children", "father", "human", "infant", "kids", "mother", "newborn", "people", "person", "station", "toddler", "wc", "young" ] -}, { - "name" : "sports_cricket", - "tags" : [ "athlete", "athletic", "ball", "bat", "cricket", "entertainment", "exercise", "game", "hobby", "social", "sports" ] -}, { - "name" : "ad_units", - "tags" : [ "Android", "OS", "ad", "banner", "cell", "device", "hardware", "iOS", "mobile", "notification", "notifications", "phone", "tablet", "top", "units" ] -}, { - "name" : "wb_twilight", - "tags" : [ "balance", "light", "lighting", "noon", "sun", "sunset", "twilight", "wb", "white" ] -}, { - "name" : "no_encryption", - "tags" : [ "disabled", "enabled", "encryption", "lock", "no", "off", "on", "password", "safety", "security", "slash" ] -}, { - "name" : "table_bar", - "tags" : [ "bar", "cafe", "round", "table" ] -}, { - "name" : "diversity_2", - "tags" : [ "committee", "diverse", "diversity", "family", "friends", "group", "groups", "heart", "humans", "network", "people", "persons", "social", "team" ] -}, { - "name" : "subway", - "tags" : [ "automobile", "bike", "car", "cars", "maps", "rail", "scooter", "subway", "train", "transportation", "travel", "tunnel", "underground", "vehicle", "vespa" ] -}, { - "name" : "browser_updated", - "tags" : [ "Android", "OS", "arrow", "browser", "chrome", "desktop", "device", "display", "download", "hardware", "iOS", "mac", "monitor", "screen", "updated", "web", "window" ] -}, { - "name" : "currency_pound", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "money", "online", "pay", "payment", "pound", "price", "shopping", "symbol" ] -}, { - "name" : "transit_enterexit", - "tags" : [ "arrow", "direction", "enterexit", "maps", "navigation", "route", "transit", "transportation" ] -}, { - "name" : "contrast", - "tags" : [ "black", "contrast", "edit", "editing", "effect", "filter", "grayscale", "image", "images", "photography", "picture", "pictures", "settings", "white" ] -}, { - "name" : "lightbulb_circle", - "tags" : [ "alert", "announcement", "idea", "info", "information", "light", "lightbulb" ] -}, { - "name" : "rectangle", - "tags" : [ "four sides", "parallelograms", "polygons", "quadrilaterals", "recangle", "shape" ] -}, { - "name" : "call_merge", - "tags" : [ "arrow", "call", "device", "merge", "mobile" ] -}, { - "name" : "hide_image", - "tags" : [ "disabled", "enabled", "hide", "image", "landscape", "mountain", "mountains", "off", "on", "photo", "photography", "picture", "slash" ] -}, { - "name" : "shield_moon", - "tags" : [ "certified", "do not disturb", "moon", "night", "privacy", "private", "protect", "protection", "security", "shield", "verified" ] -}, { - "name" : "group_off", - "tags" : [ "body", "club", "collaboration", "crowd", "gathering", "group", "human", "meeting", "off", "people", "person", "social", "teams" ] -}, { - "name" : "music_off", - "tags" : [ "audio", "audiotrack", "disabled", "enabled", "key", "music", "note", "off", "on", "slash", "sound", "track" ] -}, { - "name" : "bluetooth_disabled", - "tags" : [ "bluetooth", "cast", "connect", "connection", "device", "disabled", "enabled", "off", "offline", "on", "paring", "slash", "streaming", "symbol", "wireless" ] -}, { - "name" : "flip_to_back", - "tags" : [ "arrange", "arrangement", "back", "flip", "format", "front", "layout", "move", "order", "sort", "to" ] -}, { - "name" : "sd_card", - "tags" : [ "camera", "card", "digital", "memory", "photos", "sd", "secure", "storage" ] -}, { - "name" : "exposure_plus_1", - "tags" : [ "1", "add", "brightness", "contrast", "digit", "edit", "editing", "effect", "exposure", "image", "number", "photo", "photography", "plus", "settings", "symbol" ] -}, { - "name" : "view_array", - "tags" : [ "array", "design", "format", "grid", "layout", "view", "website" ] -}, { - "name" : "sports_mma", - "tags" : [ "arts", "athlete", "athletic", "boxing", "combat", "entertainment", "exercise", "fighting", "game", "glove", "hobby", "martial", "mixed", "mma", "social", "sports" ] -}, { - "name" : "straight", - "tags" : [ "arrow", "arrows", "direction", "directions", "maps", "navigation", "path", "route", "sign", "straight", "traffic", "up" ] -}, { - "name" : "thermostat_auto", - "tags" : [ "A", "auto", "celsius", "fahrenheit", "meter", "temp", "temperature", "thermometer", "thermostat" ] -}, { - "name" : "mobile_screen_share", - "tags" : [ "Android", "OS", "cast", "cell", "device", "hardware", "iOS", "mirror", "mobile", "monitor", "phone", "screen", "screencast", "share", "stream", "streaming", "tablet", "tv", "wireless" ] -}, { - "name" : "phone_missed", - "tags" : [ "arrow", "call", "cell", "contact", "device", "hardware", "missed", "mobile", "phone", "telephone" ] -}, { - "name" : "brunch_dining", - "tags" : [ "breakfast", "brunch", "champagne", "dining", "drink", "food", "lunch", "meal" ] -}, { - "name" : "featured_video", - "tags" : [ "advertised", "advertisement", "featured", "highlighted", "recommended", "video", "watch" ] -}, { - "name" : "merge", - "tags" : [ "arrow", "arrows", "direction", "directions", "maps", "merge", "navigation", "path", "route", "sign", "traffic" ] -}, { - "name" : "open_in_new_off", - "tags" : [ "arrow", "box", "disabled", "enabled", "export", "in", "new", "off", "on", "open", "slash", "window" ] -}, { - "name" : "hdr_auto", - "tags" : [ "A", "alphabet", "auto", "camera", "character", "circle", "dynamic", "font", "hdr", "high", "letter", "photo", "range", "symbol", "text", "type" ] -}, { - "name" : "join_inner", - "tags" : [ "circle", "command", "inner", "join", "matching", "overlap", "sql", "values" ] -}, { - "name" : "solar_power", - "tags" : [ "eco", "energy", "heat", "nest", "power", "solar", "sun", "sunny" ] -}, { - "name" : "crop_16_9", - "tags" : [ "16", "9", "adjust", "adjustments", "area", "by", "crop", "edit", "editing", "frame", "image", "images", "photo", "photos", "rectangle", "settings", "size", "square" ] -}, { - "name" : "swipe_right", - "tags" : [ "accept", "arrows", "direction", "finger", "hands", "hit", "navigation", "right", "strike", "swing", "swpie", "take" ] -}, { - "name" : "phonelink_erase", - "tags" : [ "Android", "OS", "cancel", "cell", "close", "connection", "device", "erase", "exit", "hardware", "iOS", "mobile", "no", "phone", "phonelink", "remove", "stop", "tablet", "x" ] -}, { - "name" : "smoke_free", - "tags" : [ "cigarette", "disabled", "enabled", "free", "never", "no", "off", "on", "places", "prohibited", "slash", "smoke", "smoking", "tobacco", "warning", "zone" ] -}, { - "name" : "install_desktop", - "tags" : [ "Android", "OS", "chrome", "desktop", "device", "display", "fix", "hardware", "iOS", "install", "mac", "monitor", "place", "pwa", "screen", "web", "window" ] -}, { - "name" : "shutter_speed", - "tags" : [ "aperture", "camera", "duration", "image", "lens", "photo", "photography", "photos", "picture", "setting", "shutter", "speed", "stop", "time", "timer", "watch" ] -}, { - "name" : "keyboard_hide", - "tags" : [ "arrow", "computer", "device", "down", "hardware", "hide", "input", "keyboard", "keypad", "text" ] -}, { - "name" : "exposure", - "tags" : [ "add", "brightness", "contrast", "edit", "editing", "effect", "exposure", "image", "minus", "photo", "photography", "picture", "plus", "settings", "subtract" ] -}, { - "name" : "nordic_walking", - "tags" : [ "athlete", "athletic", "body", "entertainment", "exercise", "hiking", "hobby", "human", "nordic", "people", "person", "social", "sports", "travel", "walker", "walking" ] -}, { - "name" : "umbrella", - "tags" : [ "beach", "protection", "rain", "sun", "sunny", "umbrella" ] -}, { - "name" : "move_down", - "tags" : [ "arrow", "direction", "down", "jump", "move", "navigation", "transfer" ] -}, { - "name" : "filter_2", - "tags" : [ "2", "digit", "edit", "editing", "effect", "filter", "image", "images", "multiple", "number", "photography", "picture", "pictures", "settings", "stack", "symbol" ] -}, { - "name" : "photo_album", - "tags" : [ "album", "archive", "bookmark", "image", "label", "library", "mountain", "mountains", "photo", "photography", "picture", "ribbon", "save", "tag" ] -}, { - "name" : "security_update_good", - "tags" : [ "Android", "OS", "checkmark", "device", "good", "hardware", "iOS", "mobile", "ok", "phone", "security", "tablet", "tick", "update" ] -}, { - "name" : "ssid_chart", - "tags" : [ "chart", "graph", "lines", "network", "ssid", "wifi" ] -}, { - "name" : "score", - "tags" : [ "2k", "alphabet", "analytics", "bar", "bars", "character", "chart", "data", "diagram", "digit", "font", "graph", "infographic", "letter", "measure", "metrics", "number", "score", "statistics", "symbol", "text", "tracking", "type" ] -}, { - "name" : "swipe_up", - "tags" : [ "arrows", "direction", "disable", "enable", "finger", "hands", "hit", "navigation", "strike", "swing", "swpie", "take", "up" ] -}, { - "name" : "battery_4_bar", - "tags" : [ "4", "bar", "battery", "cell", "charge", "mobile", "power" ] -}, { - "name" : "all_out", - "tags" : [ "all", "circle", "out", "shape" ] -}, { - "name" : "battery_unknown", - "tags" : [ "?", "assistance", "battery", "cell", "charge", "help", "info", "information", "mobile", "power", "punctuation", "question mark", "support", "symbol", "unknown" ] -}, { - "name" : "sports_golf", - "tags" : [ "athlete", "athletic", "ball", "club", "entertainment", "exercise", "game", "golf", "golfer", "golfing", "hobby", "social", "sports" ] -}, { - "name" : "sports_martial_arts", - "tags" : [ "arts", "athlete", "athletic", "entertainment", "exercise", "hobby", "human", "karate", "martial", "people", "person", "social", "sports" ] -}, { - "name" : "filter_tilt_shift", - "tags" : [ "blur", "center", "edit", "editing", "effect", "filter", "focus", "image", "images", "photography", "picture", "pictures", "shift", "tilt" ] -}, { - "name" : "electric_bike", - "tags" : [ "bike", "electric", "electricity", "maps", "scooter", "transportation", "travel", "vespa" ] -}, { - "name" : "border_all", - "tags" : [ "all", "border", "doc", "edit", "editing", "editor", "sheet", "spreadsheet", "stroke", "text", "type", "writing" ] -}, { - "name" : "auto_mode", - "tags" : [ "ai", "around", "arrow", "arrows", "artificial", "auto", "automatic", "automation", "custom", "direction", "genai", "inprogress", "intelligence", "load", "loading refresh", "magic", "mode", "navigation", "nest", "renew", "rotate", "smart", "spark", "sparkle", "star", "turn" ] -}, { - "name" : "hvac", - "tags" : [ "air", "conditioning", "heating", "hvac", "ventilation" ] -}, { - "name" : "scanner", - "tags" : [ "copy", "device", "hardware", "machine", "scan", "scanner" ] -}, { - "name" : "shuffle_on", - "tags" : [ "arrow", "arrows", "control", "controls", "music", "on", "random", "shuffle", "video" ] -}, { - "name" : "wifi_calling_3", - "tags" : [ "3", "calling", "cell", "cellular", "data", "internet", "mobile", "network", "phone", "speed", "wifi", "wireless" ] -}, { - "name" : "signal_wifi_off", - "tags" : [ "cell", "cellular", "data", "disabled", "enabled", "internet", "mobile", "network", "off", "on", "phone", "signal", "slash", "speed", "wifi", "wireless" ] -}, { - "name" : "girl", - "tags" : [ "body", "female", "gender", "girl", "human", "lady", "people", "person", "social", "symbol", "woman", "women" ] -}, { - "name" : "shop_2", - "tags" : [ "2", "add", "arrow", "buy", "cart", "google", "play", "purchase", "shop", "shopping" ] -}, { - "name" : "hdr_strong", - "tags" : [ "circles", "dots", "dynamic", "enhance", "hdr", "high", "range", "strong" ] -}, { - "name" : "directions_transit", - "tags" : [ "automobile", "car", "cars", "direction", "directions", "maps", "public", "rail", "subway", "train", "transit", "transportation", "vehicle" ] -}, { - "name" : "label_off", - "tags" : [ "disabled", "enabled", "favorite", "indent", "label", "library", "mail", "off", "on", "remember", "save", "slash", "stamp", "sticker", "tag", "wing" ] -}, { - "name" : "tablet", - "tags" : [ "Android", "OS", "device", "hardware", "iOS", "ipad", "mobile", "tablet", "web" ] -}, { - "name" : "5g", - "tags" : [ "5g", "alphabet", "cellular", "character", "data", "digit", "font", "letter", "mobile", "network", "number", "phone", "signal", "speed", "symbol", "text", "type", "wifi" ] -}, { - "name" : "vrpano", - "tags" : [ "angle", "image", "landscape", "mountain", "mountains", "panorama", "photo", "photography", "picture", "view", "vrpano", "wide" ] -}, { - "name" : "forward_30", - "tags" : [ "30", "arrow", "control", "controls", "digit", "fast", "forward", "music", "number", "seconds", "symbol", "video" ] -}, { - "name" : "battery_0_bar", - "tags" : [ "0", "bar", "battery", "cell", "charge", "mobile", "power" ] -}, { - "name" : "airline_seat_recline_extra", - "tags" : [ "airline", "body", "extra", "feet", "human", "leg", "legroom", "people", "person", "seat", "sitting", "space", "travel" ] -}, { - "name" : "looks", - "tags" : [ "circle", "half", "looks", "rainbow" ] -}, { - "name" : "linked_camera", - "tags" : [ "camera", "connect", "connection", "lens", "linked", "network", "photo", "photography", "picture", "signal", "signals", "sync", "wireless" ] -}, { - "name" : "paragliding", - "tags" : [ "athlete", "athletic", "body", "entertainment", "exercise", "fly", "gliding", "hobby", "human", "parachute", "paragliding", "people", "person", "sky", "skydiving", "social", "sports", "travel" ] -}, { - "name" : "electric_scooter", - "tags" : [ "bike", "electric", "maps", "scooter", "transportation", "vehicle", "vespa" ] -}, { - "name" : "settings_system_daydream", - "tags" : [ "backup", "cloud", "daydream", "drive", "settings", "storage", "system" ] -}, { - "name" : "format_indent_decrease", - "tags" : [ "align", "alignment", "decrease", "doc", "edit", "editing", "editor", "format", "indent", "indentation", "paragraph", "sheet", "spreadsheet", "text", "type", "writing" ] -}, { - "name" : "tapas", - "tags" : [ "appetizer", "brunch", "dinner", "food", "lunch", "restaurant", "snack", "tapas" ] -}, { - "name" : "brightness_3", - "tags" : [ "3", "brightness", "circle", "control", "crescent", "level", "moon", "screen" ] -}, { - "name" : "tab_unselected", - "tags" : [ "browser", "computer", "document", "documents", "folder", "internet", "tab", "tabs", "unselected", "web", "website", "window", "windows" ] -}, { - "name" : "density_small", - "tags" : [ "density", "horizontal", "lines", "rule", "rules", "small" ] -}, { - "name" : "blur_circular", - "tags" : [ "blur", "circle", "circular", "dots", "edit", "editing", "effect", "enhance", "filter" ] -}, { - "name" : "rice_bowl", - "tags" : [ "bowl", "dinner", "food", "lunch", "meal", "restaurant", "rice" ] -}, { - "name" : "rounded_corner", - "tags" : [ "adjust", "corner", "edit", "rounded", "shape", "square", "transform" ] -}, { - "name" : "person_add_disabled", - "tags" : [ "+", "account", "add", "disabled", "enabled", "face", "human", "new", "off", "offline", "on", "people", "person", "plus", "profile", "slash", "symbol", "user" ] -}, { - "name" : "music_video", - "tags" : [ "band", "music", "recording", "screen", "tv", "video", "watch" ] -}, { - "name" : "looks_6", - "tags" : [ "6", "digit", "looks", "numbers", "square", "symbol" ] -}, { - "name" : "do_not_touch", - "tags" : [ "disabled", "do", "enabled", "fingers", "gesture", "hand", "not", "off", "on", "slash", "touch" ] -}, { - "name" : "playlist_add_circle", - "tags" : [ "add", "album", "artist", "audio", "cd", "check", "circle", "collection", "list", "mark", "music", "playlist", "record", "sound", "track" ] -}, { - "name" : "domain_disabled", - "tags" : [ "apartment", "architecture", "building", "business", "company", "disabled", "domain", "enabled", "estate", "home", "internet", "maps", "off", "office", "offline", "on", "place", "real", "residence", "residential", "slash", "web", "website" ] -}, { - "name" : "flash_auto", - "tags" : [ "a", "auto", "bolt", "electric", "fast", "flash", "lightning", "thunderbolt" ] -}, { - "name" : "6_ft_apart", - "tags" : [ "6", "apart", "body", "covid", "distance", "feet", "ft", "human", "people", "person", "social" ] -}, { - "name" : "signal_wifi_bad", - "tags" : [ "bad", "bar", "cancel", "cell", "cellular", "close", "data", "exit", "internet", "mobile", "network", "no", "phone", "quit", "remove", "signal", "stop", "wifi", "wireless", "x" ] -}, { - "name" : "crisis_alert", - "tags" : [ "!", "alert", "attention", "bullseye", "caution", "crisis", "danger", "error", "exclamation", "important", "mark", "notification", "symbol", "target", "warning" ] -}, { - "name" : "queue_play_next", - "tags" : [ "+", "add", "arrow", "desktop", "device", "display", "hardware", "monitor", "new", "next", "play", "plus", "queue", "screen", "steam", "symbol", "tv" ] -}, { - "name" : "format_clear", - "tags" : [ "T", "alphabet", "character", "clear", "disabled", "doc", "edit", "editing", "editor", "enabled", "font", "format", "letter", "off", "on", "sheet", "slash", "spreadsheet", "style", "symbol", "text", "type", "writing" ] -}, { - "name" : "bus_alert", - "tags" : [ "!", "alert", "attention", "automobile", "bus", "car", "cars", "caution", "danger", "error", "exclamation", "important", "maps", "mark", "notification", "symbol", "transportation", "vehicle", "warning" ] -}, { - "name" : "party_mode", - "tags" : [ "camera", "lens", "mode", "party", "photo", "photography", "picture" ] -}, { - "name" : "snowboarding", - "tags" : [ "athlete", "athletic", "body", "entertainment", "exercise", "hobby", "human", "people", "person", "snow", "snowboarding", "social", "sports", "travel", "winter" ] -}, { - "name" : "text_rotate_vertical", - "tags" : [ "A", "alphabet", "arrow", "character", "down", "field", "font", "letter", "move", "rotate", "symbol", "text", "type", "vertical" ] -}, { - "name" : "motion_photos_auto", - "tags" : [ "A", "alphabet", "animation", "auto", "automatic", "character", "circle", "font", "gif", "letter", "live", "motion", "photos", "symbol", "text", "type", "video" ] -}, { - "name" : "crop_portrait", - "tags" : [ "adjust", "adjustments", "area", "crop", "edit", "editing", "frame", "image", "images", "photo", "photos", "portrait", "rectangle", "settings", "size", "square" ] -}, { - "name" : "thunderstorm", - "tags" : [ "bolt", "climate", "cloud", "cloudy", "lightning", "rain", "rainfall", "rainstorm", "storm", "thunder", "thunderstorm", "weather" ] -}, { - "name" : "battery_6_bar", - "tags" : [ "6", "bar", "battery", "cell", "charge", "mobile", "power" ] -}, { - "name" : "space_bar", - "tags" : [ "bar", "keyboard", "line", "space" ] -}, { - "name" : "replay_5", - "tags" : [ "5", "arrow", "arrows", "control", "controls", "digit", "five", "music", "number", "refresh", "renew", "repeat", "replay", "symbol", "video" ] -}, { - "name" : "local_car_wash", - "tags" : [ "automobile", "car", "cars", "local", "maps", "transportation", "travel", "vehicle", "wash" ] -}, { - "name" : "folder_delete", - "tags" : [ "bin", "can", "data", "delete", "doc", "document", "drive", "file", "folder", "folders", "garbage", "remove", "sheet", "slide", "storage", "trash" ] -}, { - "name" : "data_thresholding", - "tags" : [ "data", "hidden", "privacy", "thresholding", "thresold" ] -}, { - "name" : "connecting_airports", - "tags" : [ "airplane", "airplanes", "airport", "airports", "connecting", "flight", "plane", "transportation", "travel", "trip" ] -}, { - "name" : "access_alarms", - "tags" : [ ] -}, { - "name" : "tty", - "tags" : [ "call", "cell", "contact", "deaf", "device", "hardware", "impaired", "mobile", "phone", "speech", "talk", "telephone", "text", "tty" ] -}, { - "name" : "audio_file", - "tags" : [ "audio", "doc", "document", "key", "music", "note", "sound", "track" ] -}, { - "name" : "egg", - "tags" : [ "breakfast", "brunch", "egg", "food" ] -}, { - "name" : "balcony", - "tags" : [ "architecture", "balcony", "doors", "estate", "home", "house", "maps", "out", "outside", "place", "real", "residence", "residential", "stay", "terrace", "window" ] -}, { - "name" : "kitesurfing", - "tags" : [ "athlete", "athletic", "beach", "body", "entertainment", "exercise", "hobby", "human", "kitesurfing", "people", "person", "social", "sports", "surf", "travel", "water" ] -}, { - "name" : "call_missed_outgoing", - "tags" : [ "arrow", "call", "device", "missed", "mobile", "outgoing" ] -}, { - "name" : "local_hotel", - "tags" : [ "body", "hotel", "human", "local", "people", "person", "sleep", "stay", "travel", "trip" ] -}, { - "name" : "text_increase", - "tags" : [ "+", "add", "alphabet", "character", "font", "increase", "letter", "new", "plus", "resize", "symbol", "text", "type" ] -}, { - "name" : "speaker_phone", - "tags" : [ "Android", "OS", "cell", "device", "hardware", "iOS", "mobile", "phone", "sound", "speaker", "tablet", "volume" ] -}, { - "name" : "no_food", - "tags" : [ "disabled", "drink", "enabled", "fastfood", "food", "hamburger", "meal", "no", "off", "on", "slash" ] -}, { - "name" : "brightness_2", - "tags" : [ "2", "brightness", "circle", "control", "crescent", "level", "moon", "screen" ] -}, { - "name" : "mode_of_travel", - "tags" : [ "arrow", "destination", "direction", "location", "maps", "mode", "of", "pin", "place", "stop", "transportation", "travel", "trip" ] -}, { - "name" : "format_line_spacing", - "tags" : [ "align", "alignment", "doc", "edit", "editing", "editor", "format", "line", "sheet", "spacing", "spreadsheet", "text", "type", "writing" ] -}, { - "name" : "iso", - "tags" : [ "add", "edit", "editing", "effect", "image", "iso", "minus", "photography", "picture", "plus", "sensor", "shutter", "speed", "subtract" ] -}, { - "name" : "explore_off", - "tags" : [ "compass", "destination", "direction", "disabled", "east", "enabled", "explore", "location", "maps", "needle", "north", "off", "on", "slash", "south", "travel", "west" ] -}, { - "name" : "drive_file_move_rtl", - "tags" : [ "arrow", "arrows", "data", "direction", "doc", "document", "drive", "file", "folder", "folders", "left", "move", "rtl", "sheet", "side", "slide", "storage" ] -}, { - "name" : "cell_wifi", - "tags" : [ "cell", "connection", "data", "internet", "mobile", "network", "phone", "service", "signal", "wifi", "wireless" ] -}, { - "name" : "tonality", - "tags" : [ "circle", "edit", "editing", "filter", "image", "photography", "picture", "tonality" ] -}, { - "name" : "spoke", - "tags" : [ "connection", "network", "radius", "spoke" ] -}, { - "name" : "photo_filter", - "tags" : [ "ai", "artificial", "automatic", "automation", "custom", "filter", "filters", "genai", "image", "intelligence", "magic", "photo", "photography", "picture", "smart", "spark", "sparkle", "star" ] -}, { - "name" : "desktop_access_disabled", - "tags" : [ "Android", "OS", "access", "chrome", "desktop", "device", "disabled", "display", "enabled", "hardware", "iOS", "mac", "monitor", "off", "offline", "on", "screen", "slash", "web", "window" ] -}, { - "name" : "sports_gymnastics", - "tags" : [ "athlete", "athletic", "entertainment", "exercise", "gymnastics", "hobby", "social", "sports" ] -}, { - "name" : "houseboat", - "tags" : [ "architecture", "beach", "boat", "estate", "floating", "home", "house", "houseboat", "maps", "place", "real", "residence", "residential", "sea", "stay", "traveling", "vacation" ] -}, { - "name" : "fence", - "tags" : [ "backyard", "barrier", "boundaries", "boundary", "fence", "home", "house", "protection", "yard" ] -}, { - "name" : "commit", - "tags" : [ "accomplish", "bind", "circle", "commit", "dedicate", "execute", "line", "perform", "pledge" ] -}, { - "name" : "photo_size_select_small", - "tags" : [ "adjust", "album", "edit", "editing", "image", "large", "library", "mountain", "mountains", "photo", "photography", "picture", "select", "size", "small" ] -}, { - "name" : "signal_wifi_connected_no_internet_4", - "tags" : [ "4", "cell", "cellular", "connected", "data", "internet", "mobile", "network", "no", "offline", "phone", "signal", "wifi", "wireless", "x" ] -}, { - "name" : "horizontal_distribute", - "tags" : [ "alignment", "distribute", "format", "horizontal", "layout", "lines", "paragraph", "rule", "rules", "style", "text" ] -}, { - "name" : "report_off", - "tags" : [ "!", "alert", "attention", "caution", "danger", "disabled", "enabled", "error", "exclamation", "important", "mark", "notification", "octagon", "off", "offline", "on", "report", "slash", "symbol", "warning" ] -}, { - "name" : "polyline", - "tags" : [ "compose", "create", "design", "draw", "line", "polyline", "vector" ] -}, { - "name" : "art_track", - "tags" : [ "album", "art", "artist", "audio", "image", "music", "photo", "photography", "picture", "sound", "track", "tracks" ] -}, { - "name" : "crop_7_5", - "tags" : [ "5", "7", "adjust", "adjustments", "area", "by", "crop", "editing", "frame", "image", "images", "photo", "photos", "rectangle", "settings", "size", "square" ] -}, { - "name" : "filter_hdr", - "tags" : [ "camera", "edit", "editing", "effect", "filter", "hdr", "image", "mountain", "mountains", "photo", "photography", "picture" ] -}, { - "name" : "text_rotation_none", - "tags" : [ "A", "alphabet", "arrow", "character", "field", "font", "letter", "move", "none", "rotate", "symbol", "text", "type" ] -}, { - "name" : "battery_3_bar", - "tags" : [ "3", "bar", "battery", "cell", "charge", "mobile", "power" ] -}, { - "name" : "align_vertical_bottom", - "tags" : [ "align", "alignment", "bottom", "format", "layout", "lines", "paragraph", "rule", "rules", "style", "text", "vertical" ] -}, { - "name" : "stop_screen_share", - "tags" : [ "Android", "OS", "arrow", "cast", "chrome", "device", "disabled", "display", "enabled", "hardware", "iOS", "laptop", "mac", "mirror", "monitor", "off", "offline", "on", "screen", "share", "slash", "steam", "stop", "streaming", "web", "window" ] -}, { - "name" : "imagesearch_roller", - "tags" : [ "art", "image", "imagesearch", "paint", "roller", "search" ] -}, { - "name" : "bento", - "tags" : [ "bento", "box", "dinner", "food", "lunch", "meal", "restaurant", "takeout" ] -}, { - "name" : "rotate_90_degrees_cw", - "tags" : [ "90", "arrow", "arrows", "ccw", "degrees", "direction", "edit", "editing", "image", "photo", "rotate", "turn" ] -}, { - "name" : "install_mobile", - "tags" : [ "Android", "OS", "cell", "device", "hardware", "iOS", "install", "mobile", "phone", "pwa", "tablet" ] -}, { - "name" : "hearing_disabled", - "tags" : [ "accessibility", "accessible", "aid", "disabled", "ear", "enabled", "handicap", "hearing", "help", "impaired", "listen", "off", "on", "slash", "sound", "volume" ] -}, { - "name" : "video_file", - "tags" : [ "camera", "doc", "document", "film", "filming", "hardware", "image", "motion", "picture", "video", "videography" ] -}, { - "name" : "mms", - "tags" : [ "bubble", "chat", "comment", "communicate", "feedback", "image", "landscape", "message", "mms", "mountain", "mountains", "multimedia", "photo", "photography", "picture", "speech" ] -}, { - "name" : "crop_rotate", - "tags" : [ "adjust", "adjustments", "area", "arrow", "arrows", "crop", "edit", "editing", "frame", "image", "images", "photo", "photos", "rotate", "settings", "size", "turn" ] -}, { - "name" : "wheelchair_pickup", - "tags" : [ "accessibility", "accessible", "body", "handicap", "help", "human", "person", "pickup", "wheelchair" ] -}, { - "name" : "aod", - "tags" : [ "Android", "OS", "always", "aod", "device", "display", "hardware", "homescreen", "iOS", "mobile", "on", "phone", "tablet" ] -}, { - "name" : "castle", - "tags" : [ "castle", "fort", "fortress", "mansion", "palace" ] -}, { - "name" : "interpreter_mode", - "tags" : [ "interpreter", "language", "microphone", "mode", "person", "speaking", "symbol" ] -}, { - "name" : "access_alarm", - "tags" : [ ] -}, { - "name" : "forward_5", - "tags" : [ "10", "5", "arrow", "control", "controls", "digit", "fast", "forward", "music", "number", "seconds", "symbol", "video" ] -}, { - "name" : "add_to_home_screen", - "tags" : [ "Android", "OS", "add to", "arrow", "cell", "device", "hardware", "home", "iOS", "mobile", "phone", "screen", "tablet", "up" ] -}, { - "name" : "not_accessible", - "tags" : [ "accessibility", "accessible", "body", "handicap", "help", "human", "not", "person", "wheelchair" ] -}, { - "name" : "signal_cellular_0_bar", - "tags" : [ "0", "bar", "cell", "cellular", "data", "internet", "mobile", "network", "phone", "signal", "speed", "wifi", "wireless" ] -}, { - "name" : "stadium", - "tags" : [ "activity", "amphitheater", "arena", "coliseum", "event", "local", "stadium", "star", "things", "ticket" ] -}, { - "name" : "photo_size_select_large", - "tags" : [ "adjust", "album", "edit", "editing", "image", "large", "library", "mountain", "mountains", "photo", "photography", "picture", "select", "size" ] -}, { - "name" : "groups_3", - "tags" : [ "abstract", "body", "club", "collaboration", "crowd", "gathering", "groups", "human", "meeting", "people", "person", "social", "teams" ] -}, { - "name" : "snowshoeing", - "tags" : [ "body", "human", "people", "person", "snow", "snowshoe", "snowshoeing", "sports", "travel", "walking", "winter" ] -}, { - "name" : "view_kanban", - "tags" : [ "grid", "kanban", "layout", "pattern", "squares", "view" ] -}, { - "name" : "candlestick_chart", - "tags" : [ "analytics", "candlestick", "chart", "data", "diagram", "finance", "graph", "infographic", "measure", "metrics", "statistics", "tracking" ] -}, { - "name" : "filter_3", - "tags" : [ "3", "digit", "edit", "editing", "effect", "filter", "image", "images", "multiple", "number", "photography", "picture", "pictures", "settings", "stack", "symbol" ] -}, { - "name" : "arrow_outward", - "tags" : [ "app", "application", "arrow", "arrows", "components", "direction", "forward", "interface", "navigation", "right", "screen", "site", "ui", "ux", "web", "website" ] -}, { - "name" : "align_horizontal_center", - "tags" : [ "align", "alignment", "center", "format", "horizontal", "layout", "lines", "paragraph", "rule", "rules", "style", "text" ] -}, { - "name" : "flashlight_off", - "tags" : [ "disabled", "enabled", "flash", "flashlight", "light", "off", "on", "slash" ] -}, { - "name" : "security_update", - "tags" : [ "Android", "OS", "arrow", "device", "down", "download", "hardware", "iOS", "mobile", "phone", "security", "tablet", "update" ] -}, { - "name" : "iron", - "tags" : [ "appliance", "clothes", "electric", "iron", "ironing", "machine", "object" ] -}, { - "name" : "print_disabled", - "tags" : [ "disabled", "enabled", "off", "on", "paper", "print", "printer", "slash" ] -}, { - "name" : "pin_invoke", - "tags" : [ "action", "arrow", "dot", "invoke", "pin" ] -}, { - "name" : "speaker_group", - "tags" : [ "box", "electronic", "group", "loud", "multiple", "music", "sound", "speaker", "stereo", "system", "video" ] -}, { - "name" : "exposure_zero", - "tags" : [ "0", "brightness", "contrast", "digit", "edit", "editing", "effect", "exposure", "image", "number", "photo", "photography", "settings", "symbol", "zero" ] -}, { - "name" : "bungalow", - "tags" : [ "architecture", "bungalow", "cottage", "estate", "home", "house", "maps", "place", "real", "residence", "residential", "stay", "traveling" ] -}, { - "name" : "streetview", - "tags" : [ "maps", "street", "streetview", "view" ] -}, { - "name" : "swipe_down", - "tags" : [ "arrows", "direction", "disable", "down", "enable", "finger", "hands", "hit", "navigation", "strike", "swing", "swpie", "take" ] -}, { - "name" : "hdr_weak", - "tags" : [ "circles", "dots", "dynamic", "enhance", "hdr", "high", "range", "weak" ] -}, { - "name" : "css", - "tags" : [ "alphabet", "brackets", "character", "code", "css", "develop", "developer", "engineer", "engineering", "font", "html", "letter", "platform", "symbol", "text", "type" ] -}, { - "name" : "call_missed", - "tags" : [ "arrow", "call", "device", "missed", "mobile" ] -}, { - "name" : "gps_off", - "tags" : [ "destination", "direction", "disabled", "enabled", "gps", "location", "maps", "not fixed", "off", "offline", "on", "place", "pointer", "slash", "tracking" ] -}, { - "name" : "sports_hockey", - "tags" : [ "athlete", "athletic", "entertainment", "exercise", "game", "hobby", "hockey", "social", "sports", "sticks" ] -}, { - "name" : "ice_skating", - "tags" : [ "athlete", "athletic", "entertainment", "exercise", "hobby", "ice", "shoe", "skates", "skating", "social", "sports", "travel" ] -}, { - "name" : "keyboard_capslock", - "tags" : [ "arrow", "capslock", "keyboard", "up" ] -}, { - "name" : "earbuds", - "tags" : [ "accessory", "audio", "earbuds", "earphone", "headphone", "listen", "music", "sound" ] -}, { - "name" : "camera_front", - "tags" : [ "body", "camera", "front", "human", "lens", "mobile", "person", "phone", "photography", "portrait", "selfie" ] -}, { - "name" : "vertical_distribute", - "tags" : [ "alignment", "distribute", "format", "layout", "lines", "paragraph", "rule", "rules", "style", "text", "vertical" ] -}, { - "name" : "currency_ruble", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "money", "online", "pay", "payment", "price", "ruble", "shopping", "symbol" ] -}, { - "name" : "signal_wifi_statusbar_null", - "tags" : [ "cell", "cellular", "data", "internet", "mobile", "network", "null", "phone", "signal", "speed", "statusbar", "wifi", "wireless" ] -}, { - "name" : "align_horizontal_right", - "tags" : [ "align", "alignment", "format", "horizontal", "layout", "lines", "paragraph", "right", "rule", "rules", "style", "text" ] -}, { - "name" : "crop_5_4", - "tags" : [ "4", "5", "adjust", "adjustments", "area", "by", "crop", "edit", "editing settings", "frame", "image", "images", "photo", "photos", "rectangle", "size", "square" ] -}, { - "name" : "format_strikethrough", - "tags" : [ "alphabet", "character", "doc", "edit", "editing", "editor", "font", "format", "letter", "sheet", "spreadsheet", "strikethrough", "style", "symbol", "text", "type", "writing" ] -}, { - "name" : "face_6", - "tags" : [ "account", "emoji", "eyes", "face", "human", "lock", "log", "login", "logout", "people", "person", "profile", "recognition", "security", "social", "thumbnail", "unlock", "user" ] -}, { - "name" : "join_left", - "tags" : [ "circle", "command", "join", "left", "matching", "overlap", "sql", "values" ] -}, { - "name" : "explicit", - "tags" : [ "adult", "alphabet", "character", "content", "e", "explicit", "font", "language", "letter", "media", "movies", "music", "symbol", "text", "type" ] -}, { - "name" : "extension_off", - "tags" : [ "disabled", "enabled", "extended", "extension", "jigsaw", "off", "on", "piece", "puzzle", "shape", "slash" ] -}, { - "name" : "perm_camera_mic", - "tags" : [ "camera", "image", "microphone", "min", "perm", "photo", "photography", "picture", "speaker" ] -}, { - "name" : "sports_rugby", - "tags" : [ "athlete", "athletic", "ball", "entertainment", "exercise", "game", "hobby", "rugby", "social", "sports" ] -}, { - "name" : "pause_presentation", - "tags" : [ "app", "application desktop", "device", "pause", "present", "presentation", "screen", "share", "site", "slides", "web", "website", "window", "www" ] -}, { - "name" : "south_america", - "tags" : [ "continent", "landscape", "place", "region", "south america" ] -}, { - "name" : "sd_storage", - "tags" : [ "camera", "card", "data", "digital", "memory", "sd", "secure", "storage" ] -}, { - "name" : "superscript", - "tags" : [ "2", "doc", "edit", "editing", "editor", "gmail", "novitas", "sheet", "spreadsheet", "style", "superscript", "symbol", "text", "writing", "x" ] -}, { - "name" : "4g_mobiledata", - "tags" : [ "4g", "alphabet", "cellular", "character", "digit", "font", "letter", "mobile", "mobiledata", "network", "number", "phone", "signal", "speed", "symbol", "text", "type", "wifi" ] -}, { - "name" : "pinch", - "tags" : [ "arrow", "arrows", "compress", "direction", "finger", "grasp", "hand", "navigation", "nip", "pinch", "squeeze", "tweak" ] -}, { - "name" : "lock_person", - "tags" : [ ] -}, { - "name" : "grid_3x3", - "tags" : [ "3", "grid", "layout", "line", "space" ] -}, { - "name" : "mark_unread_chat_alt", - "tags" : [ "bubble", "chat", "circle", "comment", "communicate", "mark", "message", "notification", "speech", "unread" ] -}, { - "name" : "web_stories", - "tags" : [ "google", "images", "logo", "stories", "web" ] -}, { - "name" : "safety_check", - "tags" : [ "certified", "check", "clock", "privacy", "private", "protect", "protection", "safety", "schedule", "security", "shield", "time", "verified" ] -}, { - "name" : "filter_frames", - "tags" : [ "boarders", "border", "camera", "center", "edit", "editing", "effect", "filter", "filters", "focus", "frame", "frames", "image", "options", "photo", "photography", "picture" ] -}, { - "name" : "spatial_audio_off", - "tags" : [ "audio", "disabled", "enabled", "music", "note", "off", "offline", "on", "slash", "sound", "spatial" ] -}, { - "name" : "directions_subway", - "tags" : [ "automobile", "car", "cars", "direction", "directions", "maps", "public", "rail", "subway", "train", "transportation", "vehicle" ] -}, { - "name" : "reset_tv", - "tags" : [ "arrow", "device", "hardware", "monitor", "reset", "television", "tv" ] -}, { - "name" : "4k", - "tags" : [ "4000", "4K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "burst_mode", - "tags" : [ "burst", "image", "landscape", "mode", "mountain", "mountains", "multiple", "photo", "photography", "picture" ] -}, { - "name" : "chalet", - "tags" : [ "architecture", "chalet", "cottage", "estate", "home", "house", "maps", "place", "real", "residence", "residential", "stay", "traveling" ] -}, { - "name" : "battery_1_bar", - "tags" : [ "1", "bar", "battery", "cell", "charge", "mobile", "power" ] -}, { - "name" : "elderly_woman", - "tags" : [ "body", "cane", "elderly", "female", "gender", "girl", "human", "lady", "old", "people", "person", "senior", "social", "symbol", "woman", "women" ] -}, { - "name" : "headset_off", - "tags" : [ "accessory", "audio", "chat", "device", "disabled", "ear", "earphone", "enabled", "headphones", "headset", "listen", "mic", "music", "off", "on", "slash", "sound", "talk" ] -}, { - "name" : "swipe_vertical", - "tags" : [ "arrows", "direction", "finger", "hands", "hit", "navigation", "strike", "swing", "swpie", "take", "verticle" ] -}, { - "name" : "crib", - "tags" : [ "babies", "baby", "bassinet", "bed", "child", "children", "cradle", "crib", "infant", "kid", "newborn", "sleeping", "toddler" ] -}, { - "name" : "video_label", - "tags" : [ "label", "screen", "video", "window" ] -}, { - "name" : "fiber_smart_record", - "tags" : [ "circle", "dot", "fiber", "play", "record", "smart", "watch" ] -}, { - "name" : "brightness_auto", - "tags" : [ "A", "auto", "brightness", "control", "display", "level", "mobile", "monitor", "phone", "screen", "sun" ] -}, { - "name" : "margin", - "tags" : [ "design", "layout", "margin", "padding", "size", "square" ] -}, { - "name" : "punch_clock", - "tags" : [ "clock", "date", "punch", "schedule", "time", "timer", "timesheet" ] -}, { - "name" : "compass_calibration", - "tags" : [ "calibration", "compass", "connection", "internet", "location", "maps", "network", "refresh", "service", "signal", "wifi", "wireless" ] -}, { - "name" : "mosque", - "tags" : [ "islam", "islamic", "masjid", "muslim", "religion", "spiritual", "worship" ] -}, { - "name" : "medication_liquid", - "tags" : [ "+", "bottle", "doctor", "drug", "health", "hospital", "liquid", "medications", "medicine", "pharmacy", "spoon", "vessel" ] -}, { - "name" : "camera_roll", - "tags" : [ "camera", "film", "image", "library", "photo", "photography", "roll" ] -}, { - "name" : "pin_end", - "tags" : [ "action", "arrow", "dot", "end", "pin" ] -}, { - "name" : "dialer_sip", - "tags" : [ "alphabet", "call", "cell", "character", "contact", "device", "dialer", "font", "hardware", "initiation", "internet", "letter", "mobile", "over", "phone", "protocol", "routing", "session", "sip", "symbol", "telephone", "text", "type", "voice" ] -}, { - "name" : "oil_barrel", - "tags" : [ "barrel", "droplet", "gas", "gasoline", "nest", "oil", "water" ] -}, { - "name" : "disc_full", - "tags" : [ "!", "alert", "attention", "caution", "cd", "danger", "disc", "error", "exclamation", "full", "important", "mark", "music", "notification", "storage", "symbol", "warning" ] -}, { - "name" : "signal_cellular_connected_no_internet_4_bar", - "tags" : [ "!", "4", "alert", "attention", "bar", "caution", "cell", "cellular", "connected", "danger", "data", "error", "exclamation", "important", "internet", "mark", "mobile", "network", "no", "notification", "phone", "signal", "symbol", "warning", "wifi", "wireless" ] -}, { - "name" : "wind_power", - "tags" : [ "eco", "energy", "nest", "power", "wind", "windy" ] -}, { - "name" : "logo_dev", - "tags" : [ "dev", "dev.to", "logo" ] -}, { - "name" : "sledding", - "tags" : [ "athlete", "athletic", "body", "entertainment", "exercise", "hobby", "human", "people", "person", "sled", "sledding", "sledge", "snow", "social", "sports", "travel", "winter" ] -}, { - "name" : "invert_colors_off", - "tags" : [ "colors", "disabled", "drop", "droplet", "enabled", "hue", "invert", "inverted", "off", "offline", "on", "opacity", "palette", "slash", "tone", "water" ] -}, { - "name" : "wifi_lock", - "tags" : [ "cellular", "connection", "data", "internet", "lock", "locked", "mobile", "network", "password", "privacy", "private", "protection", "safety", "secure", "security", "service", "signal", "wifi", "wireless" ] -}, { - "name" : "noise_aware", - "tags" : [ "audio", "aware", "cancellation", "music", "noise", "note", "sound" ] -}, { - "name" : "face_3", - "tags" : [ "account", "emoji", "eyes", "face", "human", "lock", "log", "login", "logout", "people", "person", "profile", "recognition", "security", "social", "thumbnail", "unlock", "user" ] -}, { - "name" : "car_crash", - "tags" : [ "accident", "automobile", "car", "cars", "collision", "crash", "direction", "maps", "public", "transportation", "vehicle" ] -}, { - "name" : "comments_disabled", - "tags" : [ "bubble", "chat", "comment", "comments", "communicate", "disabled", "enabled", "feedback", "message", "off", "offline", "on", "slash", "speech" ] -}, { - "name" : "data_array", - "tags" : [ "array", "brackets", "code", "coder", "data", "parentheses" ] -}, { - "name" : "do_not_disturb_on_total_silence", - "tags" : [ "busy", "disturb", "do", "mute", "no", "not", "on total", "quiet", "silence" ] -}, { - "name" : "filter_b_and_w", - "tags" : [ "and", "b", "black", "contrast", "edit", "editing", "effect", "filter", "grayscale", "image", "images", "photography", "picture", "pictures", "settings", "w", "white" ] -}, { - "name" : "no_encryption_gmailerrorred", - "tags" : [ "disabled", "enabled", "encryption", "error", "gmail", "lock", "locked", "no", "off", "on", "slash" ] -}, { - "name" : "blur_linear", - "tags" : [ "blur", "dots", "edit", "editing", "effect", "enhance", "filter", "linear" ] -}, { - "name" : "view_cozy", - "tags" : [ "comfy", "cozy", "design", "format", "layout", "view", "web" ] -}, { - "name" : "wifi_calling", - "tags" : [ "call", "calling", "cell", "connect", "connection", "connectivity", "contact", "device", "hardware", "mobile", "phone", "signal", "telephone", "wifi", "wireless" ] -}, { - "name" : "electric_rickshaw", - "tags" : [ "automobile", "car", "cars", "electric", "india", "maps", "rickshaw", "transportation", "truck", "vehicle" ] -}, { - "name" : "rtt", - "tags" : [ "call", "real", "rrt", "text", "time" ] -}, { - "name" : "join_right", - "tags" : [ "circle", "command", "join", "matching", "overlap", "right", "sql", "values" ] -}, { - "name" : "crop_3_2", - "tags" : [ "2", "3", "adjust", "adjustments", "area", "by", "crop", "edit", "editing", "frame", "image", "images", "photo", "photos", "rectangle", "settings", "size", "square" ] -}, { - "name" : "crop_landscape", - "tags" : [ "adjust", "adjustments", "area", "crop", "edit", "editing", "frame", "image", "images", "landscape", "photo", "photos", "settings", "size" ] -}, { - "name" : "nearby_error", - "tags" : [ "!", "alert", "attention", "caution", "danger", "error", "exclamation", "important", "mark", "nearby", "notification", "symbol", "warning" ] -}, { - "name" : "airplanemode_inactive", - "tags" : [ "airplane", "airplanemode", "airport", "disabled", "enabled", "flight", "fly", "inactive", "maps", "mode", "off", "offline", "on", "slash", "transportation", "travel" ] -}, { - "name" : "airline_stops", - "tags" : [ "airline", "arrow", "destination", "direction", "layover", "location", "maps", "place", "stops", "transportation", "travel", "trip" ] -}, { - "name" : "bluetooth_audio", - "tags" : [ "audio", "bluetooth", "connect", "connection", "device", "music", "signal", "sound", "symbol" ] -}, { - "name" : "portable_wifi_off", - "tags" : [ "connection", "data", "disabled", "enabled", "internet", "network", "off", "offline", "on", "portable", "service", "signal", "slash", "wifi", "wireless" ] -}, { - "name" : "turn_right", - "tags" : [ "arrow", "arrows", "direction", "directions", "maps", "navigation", "path", "right", "route", "sign", "traffic", "turn" ] -}, { - "name" : "1x_mobiledata", - "tags" : [ "1x", "alphabet", "cellular", "character", "digit", "font", "letter", "mobile", "mobiledata", "network", "number", "phone", "signal", "speed", "symbol", "text", "type", "wifi" ] -}, { - "name" : "do_not_step", - "tags" : [ "boot", "disabled", "do", "enabled", "feet", "foot", "not", "off", "on", "shoe", "slash", "sneaker", "step", "steps" ] -}, { - "name" : "sensor_occupied", - "tags" : [ "body", "body response", "connection", "fitbit", "human", "network", "people", "person", "scan", "sensors", "signal", "smart body scan sensor", "wireless" ] -}, { - "name" : "directions_railway", - "tags" : [ "automobile", "car", "cars", "direction", "directions", "maps", "public", "railway", "train", "transportation", "vehicle" ] -}, { - "name" : "security_update_warning", - "tags" : [ "!", "Android", "OS", "alert", "attention", "caution", "danger", "device", "download", "error", "exclamation", "hardware", "iOS", "important", "mark", "mobile", "notification", "phone", "security", "symbol", "tablet", "update", "warning" ] -}, { - "name" : "pentagon", - "tags" : [ "five sides", "pentagon", "shape" ] -}, { - "name" : "wrap_text", - "tags" : [ "arrow writing", "doc", "edit", "editing", "editor", "sheet", "spreadsheet", "text", "type", "wrap", "write", "writing" ] -}, { - "name" : "no_meeting_room", - "tags" : [ "building", "disabled", "door", "doorway", "enabled", "entrance", "home", "house", "interior", "meeting", "no", "off", "office", "on", "open", "places", "room", "slash" ] -}, { - "name" : "sd_card_alert", - "tags" : [ "!", "alert", "attention", "camera", "card", "caution", "danger", "digital", "error", "exclamation", "important", "mark", "memory", "notification", "photos", "sd", "secure", "storage", "symbol", "warning" ] -}, { - "name" : "deselect", - "tags" : [ "all", "disabled", "enabled", "off", "on", "selection", "slash", "square", "tool" ] -}, { - "name" : "switch_camera", - "tags" : [ "arrow", "arrows", "camera", "photo", "photography", "picture", "switch" ] -}, { - "name" : "text_rotate_up", - "tags" : [ "A", "alphabet", "arrow", "character", "field", "font", "letter", "move", "rotate", "symbol", "text", "type", "up" ] -}, { - "name" : "sync_lock", - "tags" : [ "around", "arrow", "arrows", "lock", "locked", "password", "privacy", "private", "protection", "renew", "rotate", "safety", "secure", "security", "sync", "turn" ] -}, { - "name" : "switch_video", - "tags" : [ "arrow", "arrows", "camera", "photography", "switch", "video", "videos" ] -}, { - "name" : "border_clear", - "tags" : [ "border", "clear", "doc", "edit", "editing", "editor", "sheet", "spreadsheet", "stroke", "text", "type", "writing" ] -}, { - "name" : "repeat_one_on", - "tags" : [ "arrow", "arrows", "control", "controls", "digit", "media", "music", "number", "on", "one", "repeat", "symbol", "video" ] -}, { - "name" : "no_meals", - "tags" : [ "dining", "disabled", "eat", "enabled", "food", "fork", "knife", "meal", "meals", "no", "off", "on", "restaurant", "slash", "spoon", "utensils" ] -}, { - "name" : "align_vertical_top", - "tags" : [ "align", "alignment", "format", "layout", "lines", "paragraph", "rule", "rules", "style", "text", "top", "vertical" ] -}, { - "name" : "subscript", - "tags" : [ "2", "doc", "edit", "editing", "editor", "gmail", "novitas", "sheet", "spreadsheet", "style", "subscript", "symbol", "text", "writing", "x" ] -}, { - "name" : "font_download_off", - "tags" : [ "alphabet", "character", "disabled", "download", "enabled", "font", "letter", "off", "on", "slash", "square", "symbol", "text", "type" ] -}, { - "name" : "scoreboard", - "tags" : [ "board", "points", "score", "scoreboard", "sports" ] -}, { - "name" : "swipe_right_alt", - "tags" : [ "accept", "alt", "arrows", "direction", "finger", "hands", "hit", "navigation", "right", "strike", "swing", "swpie", "take" ] -}, { - "name" : "align_vertical_center", - "tags" : [ "align", "alignment", "center", "format", "layout", "lines", "paragraph", "rule", "rules", "style", "text", "vertical" ] -}, { - "name" : "electric_meter", - "tags" : [ "bolt", "electric", "energy", "fast", "lightning", "measure", "meter", "nest", "thunderbolt", "usage", "voltage", "volts" ] -}, { - "name" : "contact_emergency", - "tags" : [ "account", "avatar", "call", "cell", "contacts", "face", "human", "info", "information", "mobile", "people", "person", "phone", "profile", "user" ] -}, { - "name" : "signal_cellular_connected_no_internet_0_bar", - "tags" : [ "!", "0", "alert", "attention", "bar", "caution", "cell", "cellular", "connected", "danger", "data", "error", "exclamation", "important", "internet", "mark", "mobile", "network", "no", "notification", "phone", "signal", "symbol", "warning", "wifi", "wireless" ] -}, { - "name" : "sim_card_alert", - "tags" : [ "!", "alert", "attention", "camera", "card", "caution", "danger", "digital", "error", "exclamation", "important", "mark", "memory", "notification", "photos", "sd", "secure", "storage", "symbol", "warning" ] -}, { - "name" : "battery_2_bar", - "tags" : [ "2", "bar", "battery", "cell", "charge", "mobile", "power" ] -}, { - "name" : "text_rotation_angleup", - "tags" : [ "A", "alphabet", "angleup", "arrow", "character", "field", "font", "letter", "move", "rotate", "symbol", "text", "type" ] -}, { - "name" : "text_rotation_down", - "tags" : [ "A", "alphabet", "arrow", "character", "dow", "field", "font", "letter", "move", "rotate", "symbol", "text", "type" ] -}, { - "name" : "railway_alert", - "tags" : [ "!", "alert", "attention", "automobile", "bike", "car", "cars", "caution", "danger", "direction", "error", "exclamation", "important", "maps", "mark", "notification", "public", "railway", "scooter", "subway", "symbol", "train", "transportation", "vehicle", "vespa", "warning" ] -}, { - "name" : "escalator", - "tags" : [ "down", "escalator", "staircase", "up" ] -}, { - "name" : "electric_moped", - "tags" : [ "automobile", "bike", "car", "cars", "electric", "maps", "moped", "scooter", "transportation", "travel", "vehicle", "vespa" ] -}, { - "name" : "closed_caption_disabled", - "tags" : [ "accessible", "alphabet", "caption", "cc", "character", "closed", "decoder", "disabled", "enabled", "font", "language", "letter", "media", "movies", "off", "on", "slash", "subtitle", "subtitles", "symbol", "text", "tv", "type" ] -}, { - "name" : "filter_7", - "tags" : [ "7", "digit", "edit", "editing", "effect", "filter", "image", "images", "multiple", "number", "photography", "picture", "pictures", "settings", "stack", "symbol" ] -}, { - "name" : "heat_pump", - "tags" : [ "air conditioner", "cool", "energy", "furnance", "heat", "nest", "pump", "usage" ] -}, { - "name" : "dry", - "tags" : [ "air", "bathroom", "dry", "dryer", "fingers", "gesture", "hand", "wc" ] -}, { - "name" : "fork_right", - "tags" : [ "arrow", "arrows", "direction", "directions", "fork", "maps", "navigation", "path", "right", "route", "sign", "traffic" ] -}, { - "name" : "text_rotation_angledown", - "tags" : [ "A", "alphabet", "angledown", "arrow", "character", "field", "font", "letter", "move", "rotate", "symbol", "text", "type" ] -}, { - "name" : "do_not_disturb_off", - "tags" : [ "cancel", "close", "denied", "deny", "disabled", "disturb", "do", "enabled", "off", "on", "remove", "silence", "slash", "stop" ] -}, { - "name" : "screen_lock_portrait", - "tags" : [ "Android", "OS", "device", "hardware", "iOS", "lock", "mobile", "phone", "portrait", "rotate", "screen", "tablet" ] -}, { - "name" : "send_time_extension", - "tags" : [ "deliver", "dispatch", "envelop", "extension", "mail", "message", "schedule", "send", "time" ] -}, { - "name" : "keyboard_command_key", - "tags" : [ "button", "command key", "control", "keyboard" ] -}, { - "name" : "remove_from_queue", - "tags" : [ "desktop", "device", "display", "from", "hardware", "monitor", "queue", "remove", "screen", "steam" ] -}, { - "name" : "filter_4", - "tags" : [ "4", "digit", "edit", "editing", "effect", "filter", "image", "images", "multiple", "number", "photography", "picture", "pictures", "settings", "stack", "symbol" ] -}, { - "name" : "filter_9_plus", - "tags" : [ "+", "9", "digit", "edit", "editing", "effect", "filter", "image", "images", "multiple", "number", "photography", "picture", "pictures", "plus", "settings", "stack", "symbol" ] -}, { - "name" : "exposure_plus_2", - "tags" : [ "2", "add", "brightness", "contrast", "digit", "edit", "editing", "effect", "exposure", "image", "number", "photo", "photography", "plus", "settings", "symbol" ] -}, { - "name" : "surround_sound", - "tags" : [ "circle", "signal", "sound", "speaker", "surround", "system", "volumn", "wireless" ] -}, { - "name" : "airline_seat_individual_suite", - "tags" : [ "airline", "body", "business", "class", "first", "human", "individual", "people", "person", "rest", "seat", "sleep", "suite", "travel" ] -}, { - "name" : "home_max", - "tags" : [ "device", "gadget", "hardware", "home", "internet", "iot", "max", "nest", "smart", "things" ] -}, { - "name" : "phone_paused", - "tags" : [ "call", "cell", "contact", "device", "hardware", "mobile", "pause", "paused", "phone", "telephone" ] -}, { - "name" : "local_play", - "tags" : [ ] -}, { - "name" : "stroller", - "tags" : [ "baby", "care", "carriage", "child", "children", "infant", "kid", "newborn", "stroller", "toddler", "young" ] -}, { - "name" : "wifi_password", - "tags" : [ "(scan)", "[cellular", "connection", "data", "internet", "lock", "mobile]", "network", "password", "secure", "service", "signal", "wifi", "wireless" ] -}, { - "name" : "browse_gallery", - "tags" : [ "clock", "collection", "gallery", "library", "stack", "watch" ] -}, { - "name" : "system_security_update", - "tags" : [ "Android", "OS", "arrow", "cell", "device", "down", "hardware", "iOS", "mobile", "phone", "security", "system", "tablet", "update" ] -}, { - "name" : "person_2", - "tags" : [ "account", "face", "human", "people", "person", "profile", "user" ] -}, { - "name" : "screenshot_monitor", - "tags" : [ "Android", "OS", "chrome", "desktop", "device", "display", "hardware", "iOS", "mac", "monitor", "screen", "screengrab", "screenshot", "web", "window" ] -}, { - "name" : "wb_iridescent", - "tags" : [ "balance", "bright", "edit", "editing", "iridescent", "light", "lighting", "setting", "settings", "white", "wp" ] -}, { - "name" : "grid_off", - "tags" : [ "collage", "disabled", "enabled", "grid", "image", "layout", "off", "on", "slash", "view" ] -}, { - "name" : "system_security_update_warning", - "tags" : [ "!", "Android", "OS", "alert", "attention", "caution", "cell", "danger", "device", "error", "exclamation", "hardware", "iOS", "important", "mark", "mobile", "notification", "phone", "security", "symbol", "system", "tablet", "update", "warning" ] -}, { - "name" : "play_disabled", - "tags" : [ "control", "controls", "disabled", "enabled", "media", "music", "off", "on", "play", "slash", "video" ] -}, { - "name" : "php", - "tags" : [ "alphabet", "brackets", "character", "code", "css", "develop", "developer", "engineer", "engineering", "font", "html", "letter", "php", "platform", "symbol", "text", "type" ] -}, { - "name" : "phishing", - "tags" : [ "fish", "fishing", "fraud", "hook", "phishing", "scam" ] -}, { - "name" : "border_style", - "tags" : [ "border", "color", "doc", "edit", "editing", "editor", "sheet", "spreadsheet", "stroke", "style", "text", "type", "writing" ] -}, { - "name" : "motion_photos_paused", - "tags" : [ "animation", "circle", "motion", "pause", "paused", "photos", "video" ] -}, { - "name" : "headphones_battery", - "tags" : [ "accessory", "audio", "battery", "charging", "device", "ear", "earphone", "headphones", "headset", "listen", "music", "sound" ] -}, { - "name" : "monochrome_photos", - "tags" : [ "black", "camera", "image", "monochrome", "photo", "photography", "photos", "picture", "white" ] -}, { - "name" : "web_asset_off", - "tags" : [ "asset", "browser", "disabled", "enabled", "internet", "off", "on", "page", "screen", "slash", "web", "webpage", "website", "windows", "www" ] -}, { - "name" : "wifi_tethering_off", - "tags" : [ "cell", "cellular", "connection", "data", "disabled", "enabled", "internet", "mobile", "network", "off", "offline", "on", "phone", "scan", "service", "signal", "slash", "speed", "tethering", "wifi", "wireless" ] -}, { - "name" : "text_decrease", - "tags" : [ "-", "alphabet", "character", "decrease", "font", "letter", "minus", "remove", "resize", "subtract", "symbol", "text", "type" ] -}, { - "name" : "view_comfy_alt", - "tags" : [ "alt", "comfy", "cozy", "design", "format", "layout", "view", "web" ] -}, { - "name" : "photo_camera_back", - "tags" : [ "back", "camera", "image", "landscape", "mountain", "mountains", "photo", "photography", "picture", "rear" ] -}, { - "name" : "folder_off", - "tags" : [ "data", "disabled", "doc", "document", "drive", "enabled", "file", "folder", "folders", "off", "on", "online", "sheet", "slash", "slide", "storage" ] -}, { - "name" : "gas_meter", - "tags" : [ "droplet", "energy", "gas", "measure", "meter", "nest", "usage", "water" ] -}, { - "name" : "edgesensor_high", - "tags" : [ "Android", "OS", "cell", "device", "edge", "hardware", "high", "iOS", "mobile", "move", "phone", "sensitivity", "sensor", "tablet", "vibrate" ] -}, { - "name" : "filter_5", - "tags" : [ "5", "digit", "edit", "editing", "effect", "filter", "image", "images", "multiple", "number", "photography", "picture", "pictures", "settings", "stack", "symbol" ] -}, { - "name" : "stay_current_landscape", - "tags" : [ "Android", "OS", "current", "device", "hardware", "iOS", "landscape", "mobile", "phone", "stay", "tablet" ] -}, { - "name" : "sip", - "tags" : [ "alphabet", "call", "character", "dialer", "font", "initiation", "internet", "letter", "over", "phone", "protocol", "routing", "session", "sip", "symbol", "text", "type", "voice" ] -}, { - "name" : "power_input", - "tags" : [ "input", "lines", "power", "supply" ] -}, { - "name" : "smart_screen", - "tags" : [ "Android", "OS", "airplay", "cast", "cell", "connect", "device", "hardware", "iOS", "mobile", "phone", "screen", "screencast", "smart", "stream", "tablet", "video" ] -}, { - "name" : "mail_lock", - "tags" : [ "email", "envelop", "letter", "lock", "locked", "mail", "message", "password", "privacy", "private", "protection", "safety", "secure", "security", "send" ] -}, { - "name" : "dataset", - "tags" : [ ] -}, { - "name" : "nat", - "tags" : [ "communication", "nat" ] -}, { - "name" : "do_disturb_off", - "tags" : [ "cancel", "close", "denied", "deny", "disabled", "disturb", "do", "enabled", "off", "on", "remove", "silence", "slash", "stop" ] -}, { - "name" : "no_drinks", - "tags" : [ "alcohol", "beverage", "bottle", "cocktail", "drink", "drinks", "food", "liquor", "no", "wine" ] -}, { - "name" : "bike_scooter", - "tags" : [ "automobile", "bike", "car", "cars", "maps", "scooter", "transportation", "vehicle", "vespa" ] -}, { - "name" : "dock", - "tags" : [ "Android", "OS", "cell", "charging", "connector", "device", "dock", "hardware", "iOS", "mobile", "phone", "power", "station", "tablet" ] -}, { - "name" : "face_2", - "tags" : [ "account", "emoji", "eyes", "face", "human", "lock", "log", "login", "logout", "people", "person", "profile", "recognition", "security", "social", "thumbnail", "unlock", "user" ] -}, { - "name" : "face_retouching_off", - "tags" : [ "disabled", "edit", "editing", "effect", "emoji", "emotion", "enabled", "face", "faces", "image", "natural", "off", "on", "photo", "photography", "retouch", "retouching", "settings", "slash", "tag" ] -}, { - "name" : "auto_fix_off", - "tags" : [ "ai", "artificial", "auto", "automatic", "automation", "custom", "disabled", "edit", "enabled", "erase", "fix", "genai", "intelligence", "magic", "modify", "off", "on", "slash", "smart", "spark", "sparkle", "star", "wand" ] -}, { - "name" : "airline_seat_flat", - "tags" : [ "airline", "body", "business", "class", "first", "flat", "human", "people", "person", "rest", "seat", "sleep", "travel" ] -}, { - "name" : "phone_locked", - "tags" : [ "call", "cell", "contact", "device", "hardware", "lock", "locked", "mobile", "password", "phone", "privacy", "private", "protection", "safety", "secure", "security", "telephone" ] -}, { - "name" : "network_locked", - "tags" : [ "alert", "available", "cellular", "connection", "data", "error", "internet", "lock", "locked", "mobile", "network", "not", "privacy", "private", "protection", "restricted", "safety", "secure", "security", "service", "signal", "warning", "wifi", "wireless" ] -}, { - "name" : "padding", - "tags" : [ "design", "layout", "margin", "padding", "size", "square" ] -}, { - "name" : "browser_not_supported", - "tags" : [ "browser", "disabled", "enabled", "internet", "not", "off", "on", "page", "screen", "site", "slash", "supported", "web", "website", "www" ] -}, { - "name" : "border_outer", - "tags" : [ "border", "doc", "edit", "editing", "editor", "outer", "sheet", "spreadsheet", "stroke", "text", "type", "writing" ] -}, { - "name" : "exposure_neg_1", - "tags" : [ "1", "brightness", "contrast", "digit", "edit", "editing", "effect", "exposure", "image", "neg", "negative", "number", "photo", "photography", "settings", "symbol" ] -}, { - "name" : "view_compact_alt", - "tags" : [ "alt", "compact", "design", "format", "layout dense", "view", "web" ] -}, { - "name" : "pest_control_rodent", - "tags" : [ "control", "exterminator", "mice", "pest", "rodent" ] -}, { - "name" : "swipe_down_alt", - "tags" : [ "alt", "arrows", "direction", "disable", "down", "enable", "finger", "hands", "hit", "navigation", "strike", "swing", "swpie", "take" ] -}, { - "name" : "airlines", - "tags" : [ "airlines", "airplane", "airport", "flight", "plane", "transportation", "travel", "trip" ] -}, { - "name" : "turn_left", - "tags" : [ "arrow", "arrows", "direction", "directions", "left", "maps", "navigation", "path", "route", "sign", "traffic", "turn" ] -}, { - "name" : "sd", - "tags" : [ "alphabet", "camera", "card", "character", "data", "device", "digital", "drive", "flash", "font", "image", "letter", "memory", "photo", "sd", "secure", "symbol", "text", "type" ] -}, { - "name" : "near_me_disabled", - "tags" : [ "destination", "direction", "disabled", "enabled", "location", "maps", "me", "navigation", "near", "off", "on", "pin", "place", "point", "slash" ] -}, { - "name" : "face_4", - "tags" : [ "account", "emoji", "eyes", "face", "human", "lock", "log", "login", "logout", "people", "person", "profile", "recognition", "security", "social", "thumbnail", "unlock", "user" ] -}, { - "name" : "stay_primary_landscape", - "tags" : [ "Android", "OS", "current", "device", "hardware", "iOS", "landscape", "mobile", "phone", "primary", "stay", "tablet" ] -}, { - "name" : "4g_plus_mobiledata", - "tags" : [ "4g", "alphabet", "cellular", "character", "digit", "font", "letter", "mobile", "mobiledata", "network", "number", "phone", "plus", "signal", "speed", "symbol", "text", "type", "wifi" ] -}, { - "name" : "snowmobile", - "tags" : [ "automobile", "car", "direction", "skimobile", "snow", "snowmobile", "social", "sports", "transportation", "travel", "vehicle", "winter" ] -}, { - "name" : "sign_language", - "tags" : [ "communication", "deaf", "fingers", "gesture", "hand", "language", "sign" ] -}, { - "name" : "network_ping", - "tags" : [ "alert", "available", "cellular", "connection", "data", "internet", "ip", "mobile", "network", "ping", "service", "signal", "wifi", "wireless" ] -}, { - "name" : "signal_cellular_off", - "tags" : [ "cell", "cellular", "data", "disabled", "enabled", "internet", "mobile", "network", "off", "offline", "on", "phone", "signal", "slash", "wifi", "wireless" ] -}, { - "name" : "signal_cellular_nodata", - "tags" : [ "cell", "cellular", "data", "internet", "mobile", "network", "no", "nodata", "offline", "phone", "quit", "signal", "wifi", "wireless", "x" ] -}, { - "name" : "no_sim", - "tags" : [ "camera", "card", "device", "eject", "insert", "memory", "no", "phone", "sim", "storage" ] -}, { - "name" : "signal_wifi_4_bar_lock", - "tags" : [ "4", "bar", "cell", "cellular", "data", "internet", "lock", "locked", "mobile", "network", "password", "phone", "privacy", "private", "protection", "safety", "secure", "security", "signal", "wifi", "wireless" ] -}, { - "name" : "missed_video_call", - "tags" : [ "arrow", "call", "camera", "film", "filming", "hardware", "image", "missed", "motion", "picture", "record", "video", "videography" ] -}, { - "name" : "lte_mobiledata", - "tags" : [ "alphabet", "character", "data", "font", "internet", "letter", "lte", "mobile", "network", "speed", "symbol", "text", "type", "wifi", "wireless" ] -}, { - "name" : "earbuds_battery", - "tags" : [ "accessory", "audio", "battery", "charging", "earbuds", "earphone", "headphone", "listen", "music", "sound" ] -}, { - "name" : "panorama_photosphere", - "tags" : [ "angle", "horizontal", "image", "panorama", "photo", "photography", "photosphere", "picture", "wide" ] -}, { - "name" : "no_crash", - "tags" : [ "accident", "auto", "automobile", "car", "cars", "check", "collision", "confirm", "correct", "crash", "direction", "done", "enter", "maps", "mark", "no", "ok", "okay", "select", "tick", "transportation", "vehicle", "yes" ] -}, { - "name" : "add_alarm", - "tags" : [ ] -}, { - "name" : "directions_transit_filled", - "tags" : [ "automobile", "car", "cars", "direction", "directions", "filled", "maps", "public", "rail", "subway", "train", "transit", "transportation", "vehicle" ] -}, { - "name" : "u_turn_left", - "tags" : [ "arrow", "arrows", "direction", "directions", "left", "maps", "navigation", "path", "route", "sign", "traffic", "u-turn" ] -}, { - "name" : "line_axis", - "tags" : [ "axis", "dash", "horizontal", "line", "stroke", "vertical" ] -}, { - "name" : "density_large", - "tags" : [ "density", "horizontal", "large", "lines", "rule", "rules" ] -}, { - "name" : "location_disabled", - "tags" : [ "destination", "direction", "disabled", "enabled", "location", "maps", "off", "on", "pin", "place", "pointer", "slash", "stop", "tracking" ] -}, { - "name" : "bluetooth_drive", - "tags" : [ "automobile", "bluetooth", "car", "cars", "cast", "connect", "connection", "device", "drive", "maps", "paring", "streaming", "symbol", "transportation", "travel", "vehicle", "wireless" ] -}, { - "name" : "30fps", - "tags" : [ "30fps", "alphabet", "camera", "character", "digit", "font", "fps", "frames", "letter", "number", "symbol", "text", "type", "video" ] -}, { - "name" : "no_luggage", - "tags" : [ "bag", "baggage", "carry", "disabled", "enabled", "luggage", "no", "off", "on", "slash", "suitcase", "travel" ] -}, { - "name" : "leak_remove", - "tags" : [ "connection", "data", "disabled", "enabled", "leak", "link", "network", "off", "offline", "on", "remove", "service", "signals", "slash", "synce", "wireless" ] -}, { - "name" : "filter_8", - "tags" : [ "8", "digit", "edit", "editing", "effect", "filter", "image", "images", "multiple", "number", "photography", "picture", "pictures", "settings", "stack", "symbol" ] -}, { - "name" : "mobile_off", - "tags" : [ "Android", "OS", "cell", "device", "disabled", "enabled", "hardware", "iOS", "mobile", "off", "on", "phone", "silence", "slash", "tablet" ] -}, { - "name" : "key_off", - "tags" : [ "disabled", "enabled", "key", "lock", "off", "offline", "on", "password", "slash", "unlock" ] -}, { - "name" : "signal_cellular_null", - "tags" : [ "cell", "cellular", "data", "internet", "mobile", "network", "null", "phone", "signal", "wifi", "wireless" ] -}, { - "name" : "phonelink_off", - "tags" : [ "Android", "OS", "chrome", "computer", "connect", "desktop", "device", "disabled", "enabled", "hardware", "iOS", "link", "mac", "mobile", "off", "on", "phone", "phonelink", "slash", "sync", "tablet", "web", "windows" ] -}, { - "name" : "filter_9", - "tags" : [ "9", "digit", "edit", "editing", "effect", "filter", "image", "images", "multiple", "number", "photography", "picture", "pictures", "settings", "stack", "symbol" ] -}, { - "name" : "home_mini", - "tags" : [ "Internet", "device", "gadget", "hardware", "home", "iot", "mini", "nest", "smart", "things" ] -}, { - "name" : "on_device_training", - "tags" : [ "arrow", "bulb", "call", "cell", "contact", "device", "hardware", "idea", "inprogress", "light", "load", "loading", "mobile", "model", "phone", "refresh", "renew", "restore", "reverse", "rotate", "telephone", "training" ] -}, { - "name" : "egg_alt", - "tags" : [ "breakfast", "brunch", "egg", "food" ] -}, { - "name" : "media_bluetooth_on", - "tags" : [ "bluetooth", "connect", "connection", "connectivity", "device", "disabled", "enabled", "media", "music", "note", "off", "on", "online", "paring", "signal", "slash", "symbol", "wireless" ] -}, { - "name" : "10k", - "tags" : [ "10000", "10K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "video_stable", - "tags" : [ "film", "filming", "recording", "setting", "stability", "stable", "taping", "video" ] -}, { - "name" : "add_home", - "tags" : [ ] -}, { - "name" : "no_transfer", - "tags" : [ "automobile", "bus", "car", "cars", "direction", "disabled", "enabled", "maps", "no", "off", "on", "public", "slash", "transfer", "transportation", "vehicle" ] -}, { - "name" : "timer_10", - "tags" : [ "10", "digits", "duration", "number", "numbers", "seconds", "time", "timer" ] -}, { - "name" : "directions_subway_filled", - "tags" : [ "automobile", "car", "cars", "direction", "directions", "filled", "maps", "public", "rail", "subway", "train", "transportation", "vehicle" ] -}, { - "name" : "wb_shade", - "tags" : [ "balance", "house", "light", "lighting", "shade", "wb", "white" ] -}, { - "name" : "swipe_left_alt", - "tags" : [ "alt", "arrow", "arrows", "finger", "hand", "hit", "left", "navigation", "reject", "strike", "swing", "swipe", "take" ] -}, { - "name" : "filter_6", - "tags" : [ "6", "digit", "edit", "editing", "effect", "filter", "image", "images", "multiple", "number", "photography", "picture", "pictures", "settings", "stack", "symbol" ] -}, { - "name" : "cyclone", - "tags" : [ "crisis", "disaster", "natural", "rain", "storm", "weather", "wind", "winds" ] -}, { - "name" : "network_wifi_1_bar", - "tags" : [ ] -}, { - "name" : "directions_railway_filled", - "tags" : [ "automobile", "car", "cars", "direction", "directions", "filled", "maps", "public", "railway", "train", "transportation", "vehicle" ] -}, { - "name" : "wifi_find", - "tags" : [ "(scan)", "[cellular", "connection", "data", "detect", "discover", "find", "internet", "look", "magnifying glass", "mobile]", "network", "notice", "search", "service", "signal", "wifi", "wireless" ] -}, { - "name" : "blur_off", - "tags" : [ "blur", "disabled", "dots", "edit", "editing", "effect", "enabled", "enhance", "off", "on", "slash" ] -}, { - "name" : "motion_photos_off", - "tags" : [ "animation", "circle", "disabled", "enabled", "motion", "off", "on", "photos", "slash", "video" ] -}, { - "name" : "lyrics", - "tags" : [ "audio", "bubble", "chat", "comment", "communicate", "feedback", "key", "lyrics", "message", "music", "note", "song", "sound", "speech", "track" ] -}, { - "name" : "raw_on", - "tags" : [ "alphabet", "character", "disabled", "enabled", "font", "image", "letter", "off", "on", "original", "photo", "photography", "raw", "slash", "symbol", "text", "type" ] -}, { - "name" : "flight_class", - "tags" : [ "airplane", "business", "class", "first", "flight", "plane", "seat", "transportation", "travel", "trip", "window" ] -}, { - "name" : "insert_page_break", - "tags" : [ "break", "doc", "document", "file", "page", "paper" ] -}, { - "name" : "rsvp", - "tags" : [ "alphabet", "character", "font", "invitation", "invite", "letter", "plaît", "respond", "rsvp", "répondez", "sil", "symbol", "text", "type", "vous" ] -}, { - "name" : "tire_repair", - "tags" : [ "auto", "automobile", "car", "cars", "gauge", "mechanic", "pressure", "repair", "tire", "vehicle" ] -}, { - "name" : "swipe_up_alt", - "tags" : [ "alt", "arrows", "direction", "disable", "enable", "finger", "hands", "hit", "navigation", "strike", "swing", "swpie", "take", "up" ] -}, { - "name" : "3g_mobiledata", - "tags" : [ "3g", "alphabet", "cellular", "character", "digit", "font", "letter", "mobile", "mobiledata", "network", "number", "phone", "signal", "speed", "symbol", "text", "type", "wifi" ] -}, { - "name" : "tv_off", - "tags" : [ "Android", "OS", "chrome", "desktop", "device", "disabled", "enabled", "hardware", "iOS", "mac", "monitor", "off", "on", "slash", "television", "tv", "web", "window" ] -}, { - "name" : "hdr_on", - "tags" : [ "add", "alphabet", "character", "dynamic", "enhance", "font", "hdr", "high", "letter", "on", "plus", "range", "select", "symbol", "text", "type" ] -}, { - "name" : "add_home_work", - "tags" : [ ] -}, { - "name" : "motion_photos_pause", - "tags" : [ "animation", "circle", "motion", "pause", "paused", "photos", "video" ] -}, { - "name" : "edgesensor_low", - "tags" : [ "Android", "cell", "device", "edge", "hardware", "iOS", "low", "mobile", "move", "phone", "sensitivity", "sensor", "tablet", "vibrate" ] -}, { - "name" : "grid_goldenratio", - "tags" : [ "golden", "goldenratio", "grid", "layout", "lines", "ratio", "space" ] -}, { - "name" : "network_wifi_3_bar", - "tags" : [ ] -}, { - "name" : "temple_buddhist", - "tags" : [ "buddha", "buddhism", "buddhist", "monastery", "religion", "spiritual", "temple", "worship" ] -}, { - "name" : "airline_seat_flat_angled", - "tags" : [ "airline", "angled", "body", "business", "class", "first", "flat", "human", "people", "person", "rest", "seat", "sleep", "travel" ] -}, { - "name" : "fort", - "tags" : [ "castle", "fort", "fortress", "mansion", "palace" ] -}, { - "name" : "spatial_tracking", - "tags" : [ "audio", "disabled", "enabled", "music", "note", "off", "offline", "on", "slash", "sound", "spatial", "tracking" ] -}, { - "name" : "screen_lock_rotation", - "tags" : [ "Android", "OS", "arrow", "device", "hardware", "iOS", "lock", "mobile", "phone", "rotate", "rotation", "screen", "tablet", "turn" ] -}, { - "name" : "fiber_pin", - "tags" : [ "alphabet", "character", "fiber", "font", "letter", "network", "pin", "symbol", "text", "type" ] -}, { - "name" : "phone_bluetooth_speaker", - "tags" : [ "bluetooth", "call", "cell", "connect", "connection", "connectivity", "contact", "device", "hardware", "mobile", "phone", "signal", "speaker", "symbol", "telephone", "wireless" ] -}, { - "name" : "vignette", - "tags" : [ "border", "edit", "editing", "filter", "gradient", "image", "photo", "photography", "setting", "vignette" ] -}, { - "name" : "panorama_horizontal", - "tags" : [ "angle", "horizontal", "image", "panorama", "photo", "photography", "picture", "wide" ] -}, { - "name" : "propane_tank", - "tags" : [ "bbq", "gas", "grill", "nest", "propane", "tank" ] -}, { - "name" : "kebab_dining", - "tags" : [ "dining", "dinner", "food", "kebab", "meal", "meat", "skewer" ] -}, { - "name" : "developer_board_off", - "tags" : [ "board", "chip", "computer", "developer", "development", "disabled", "enabled", "hardware", "microchip", "off", "on", "processor", "slash" ] -}, { - "name" : "adf_scanner", - "tags" : [ "adf", "document", "feeder", "machine", "office", "scan", "scanner" ] -}, { - "name" : "no_cell", - "tags" : [ "Android", "OS", "cell", "device", "disabled", "enabled", "hardware", "iOS", "mobile", "no", "off", "on", "phone", "slash", "tablet" ] -}, { - "name" : "dirty_lens", - "tags" : [ "camera", "dirty", "lens", "photo", "photography", "picture", "splat" ] -}, { - "name" : "usb_off", - "tags" : [ "cable", "connection", "device", "off", "usb", "wire" ] -}, { - "name" : "image_aspect_ratio", - "tags" : [ "aspect", "image", "photo", "photography", "picture", "ratio", "rectangle", "square" ] -}, { - "name" : "30fps_select", - "tags" : [ "30", "camera", "digits", "fps", "frame", "frequency", "image", "numbers", "per", "rate", "second", "seconds", "select", "video" ] -}, { - "name" : "60fps", - "tags" : [ "60fps", "camera", "digit", "fps", "frames", "number", "symbol", "video" ] -}, { - "name" : "screen_lock_landscape", - "tags" : [ "Android", "OS", "device", "hardware", "iOS", "landscape", "lock", "mobile", "phone", "rotate", "screen", "tablet" ] -}, { - "name" : "lte_plus_mobiledata", - "tags" : [ "+", "alphabet", "character", "data", "font", "internet", "letter", "lte", "mobile", "network", "plus", "speed", "symbol", "text", "type", "wifi", "wireless" ] -}, { - "name" : "piano_off", - "tags" : [ "disabled", "enabled", "instrument", "keyboard", "keys", "music", "musical", "off", "on", "piano", "slash", "social" ] -}, { - "name" : "unfold_more_double", - "tags" : [ "arrow", "arrows", "chevron", "collapse", "direction", "double", "down", "expand", "expandable", "list", "more", "navigation", "unfold" ] -}, { - "name" : "deblur", - "tags" : [ "adjust", "deblur", "edit", "editing", "enhance", "face", "image", "lines", "photo", "photography", "sharpen" ] -}, { - "name" : "person_4", - "tags" : [ "account", "face", "human", "people", "person", "profile", "user" ] -}, { - "name" : "spatial_audio", - "tags" : [ "audio", "music", "note", "sound", "spatial" ] -}, { - "name" : "camera_rear", - "tags" : [ "camera", "front", "lens", "mobile", "phone", "photo", "photography", "picture", "portrait", "rear", "selfie" ] -}, { - "name" : "timer_10_select", - "tags" : [ "10", "alphabet", "camera", "character", "digit", "font", "letter", "number", "seconds", "select", "symbol", "text", "timer", "type" ] -}, { - "name" : "face_5", - "tags" : [ "account", "emoji", "eyes", "face", "human", "lock", "log", "login", "logout", "people", "person", "profile", "recognition", "security", "social", "thumbnail", "unlock", "user" ] -}, { - "name" : "minor_crash", - "tags" : [ "accident", "auto", "automobile", "car", "cars", "collision", "directions", "maps", "public", "transportation", "vehicle" ] -}, { - "name" : "sos", - "tags" : [ "font", "help", "letters", "save", "sos", "text", "type" ] -}, { - "name" : "videogame_asset_off", - "tags" : [ "asset", "console", "controller", "device", "disabled", "enabled", "game", "gamepad", "gaming", "off", "on", "playstation", "slash", "video", "videogame" ] -}, { - "name" : "flood", - "tags" : [ "crisis", "disaster", "natural", "rain", "storm", "weather" ] -}, { - "name" : "60fps_select", - "tags" : [ "60", "camera", "digits", "fps", "frame", "frequency", "numbers", "per", "rate", "second", "seconds", "select", "video" ] -}, { - "name" : "timer_3", - "tags" : [ "3", "digits", "duration", "number", "numbers", "seconds", "time", "timer" ] -}, { - "name" : "vpn_key_off", - "tags" : [ "code", "disabled", "enabled", "key", "lock", "network", "off", "offline", "on", "passcode", "password", "slash", "unlock", "vpn" ] -}, { - "name" : "directions_off", - "tags" : [ "arrow", "directions", "disabled", "enabled", "maps", "off", "on", "right", "route", "sign", "slash", "traffic" ] -}, { - "name" : "emergency_share", - "tags" : [ "alert", "attention", "caution", "danger", "emergency", "important", "notification", "share", "warning" ] -}, { - "name" : "panorama_wide_angle_select", - "tags" : [ "angle", "image", "panorama", "photo", "photography", "picture", "select", "wide" ] -}, { - "name" : "airline_seat_legroom_normal", - "tags" : [ "airline", "body", "feet", "human", "leg", "legroom", "normal", "people", "person", "seat", "sitting", "space", "travel" ] -}, { - "name" : "fiber_dvr", - "tags" : [ "alphabet", "character", "digital", "dvr", "electronics", "fiber", "font", "letter", "network", "record", "recorder", "symbol", "text", "tv", "type", "video" ] -}, { - "name" : "person_3", - "tags" : [ "account", "face", "human", "people", "person", "profile", "user" ] -}, { - "name" : "scuba_diving", - "tags" : [ "diving", "entertainment", "exercise", "hobby", "scuba", "social", "swim", "swimming" ] -}, { - "name" : "signal_cellular_no_sim", - "tags" : [ "camera", "card", "cellular", "chip", "device", "disabled", "enabled", "memory", "no", "off", "offline", "on", "phone", "signal", "sim", "slash", "storage" ] -}, { - "name" : "24mp", - "tags" : [ "24", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "exposure_neg_2", - "tags" : [ "2", "brightness", "contrast", "digit", "edit", "editing", "effect", "exposure", "image", "neg", "negative", "number", "photo", "photography", "settings", "symbol" ] -}, { - "name" : "network_wifi_2_bar", - "tags" : [ ] -}, { - "name" : "wifi_2_bar", - "tags" : [ "2", "bar", "cell", "cellular", "connection", "data", "internet", "mobile", "network", "phone", "scan", "service", "signal", "wifi", "wireless" ] -}, { - "name" : "u_turn_right", - "tags" : [ "arrow", "arrows", "direction", "directions", "maps", "navigation", "path", "right", "route", "sign", "traffic", "u-turn" ] -}, { - "name" : "currency_yuan", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "money", "online", "pay", "payment", "price", "shopping", "symbol", "yuan" ] -}, { - "name" : "currency_lira", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "lira", "money", "online", "pay", "payment", "price", "shopping", "symbol" ] -}, { - "name" : "no_flash", - "tags" : [ "bolt", "camera", "disabled", "enabled", "flash", "image", "lightning", "no", "off", "on", "photo", "photography", "picture", "slash", "thunderbolt" ] -}, { - "name" : "temple_hindu", - "tags" : [ "hindu", "hinduism", "hindus", "mandir", "religion", "spiritual", "temple", "worship" ] -}, { - "name" : "mode_fan_off", - "tags" : [ "air conditioner", "cool", "disabled", "enabled", "fan", "nest", "off", "on", "slash" ] -}, { - "name" : "airline_seat_legroom_extra", - "tags" : [ "airline", "body", "extra", "feet", "human", "leg", "legroom", "people", "person", "seat", "sitting", "space", "travel" ] -}, { - "name" : "4k_plus", - "tags" : [ "+", "4000", "4K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "plus", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "border_inner", - "tags" : [ "border", "doc", "edit", "editing", "editor", "inner", "sheet", "spreadsheet", "stroke", "text", "type", "writing" ] -}, { - "name" : "wifi_tethering_error", - "tags" : [ "!", "alert", "attention", "caution", "cell", "cellular", "connection", "danger", "data", "error", "exclamation", "important", "internet", "mark", "mobile", "network", "notification", "phone", "rounded", "scan", "service", "signal", "speed", "symbol", "tethering", "warning", "wifi", "wireless" ] -}, { - "name" : "airline_seat_legroom_reduced", - "tags" : [ "airline", "body", "feet", "human", "leg", "legroom", "people", "person", "reduced", "seat", "sitting", "space", "travel" ] -}, { - "name" : "synagogue", - "tags" : [ "jew", "jewish", "religion", "shul", "spiritual", "temple", "worship" ] -}, { - "name" : "border_left", - "tags" : [ "border", "doc", "edit", "editing", "editor", "left", "sheet", "spreadsheet", "stroke", "text", "type", "writing" ] -}, { - "name" : "autofps_select", - "tags" : [ "A", "alphabet", "auto", "character", "font", "fps", "frame", "frequency", "letter", "per", "rate", "second", "seconds", "select", "symbol", "text", "type" ] -}, { - "name" : "signal_cellular_alt_2_bar", - "tags" : [ "2", "bar", "cell", "cellular", "data", "internet", "mobile", "network", "phone", "signal", "speed", "wifi", "wireless" ] -}, { - "name" : "g_mobiledata", - "tags" : [ "alphabet", "character", "data", "font", "g", "letter", "mobile", "network", "service", "symbol", "text", "type" ] -}, { - "name" : "1k", - "tags" : [ "1000", "1K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "format_textdirection_l_to_r", - "tags" : [ "align", "alignment", "doc", "edit", "editing", "editor", "format", "ltr", "sheet", "spreadsheet", "text", "textdirection", "type", "writing" ] -}, { - "name" : "border_bottom", - "tags" : [ "border", "bottom", "doc", "edit", "editing", "editor", "sheet", "spreadsheet", "stroke", "text", "type", "writing" ] -}, { - "name" : "fork_left", - "tags" : [ "arrow", "arrows", "direction", "directions", "fork", "left", "maps", "navigation", "path", "route", "sign", "traffic" ] -}, { - "name" : "severe_cold", - "tags" : [ "!", "alert", "attention", "caution", "climate", "cold", "crisis", "danger", "disaster", "error", "exclamation", "important", "notification", "severe", "snow", "snowflake", "warning", "weather", "winter" ] -}, { - "name" : "tsunami", - "tags" : [ "crisis", "disaster", "flood", "rain", "storm", "tsunami", "weather" ] -}, { - "name" : "signal_cellular_alt_1_bar", - "tags" : [ "1", "bar", "cell", "cellular", "data", "internet", "mobile", "network", "phone", "signal", "speed", "wifi", "wireless" ] -}, { - "name" : "border_vertical", - "tags" : [ "border", "doc", "edit", "editing", "editor", "sheet", "spreadsheet", "stroke", "text", "type", "vertical", "writing" ] -}, { - "name" : "turn_sharp_right", - "tags" : [ "arrow", "arrows", "direction", "directions", "maps", "navigation", "path", "right", "route", "sharp", "sign", "traffic", "turn" ] -}, { - "name" : "no_backpack", - "tags" : [ "accessory", "backpack", "bag", "bookbag", "knapsack", "no", "pack", "travel" ] -}, { - "name" : "remove_road", - "tags" : [ "-", "cancel", "close", "destination", "direction", "exit", "highway", "maps", "minus", "new", "no", "remove", "road", "stop", "street", "symbol", "traffic", "x" ] -}, { - "name" : "timer_3_select", - "tags" : [ "3", "alphabet", "camera", "character", "digit", "font", "letter", "number", "seconds", "select", "symbol", "text", "timer", "type" ] -}, { - "name" : "roller_skating", - "tags" : [ "athlete", "athletic", "entertainment", "exercise", "hobby", "roller", "shoe", "skate", "skates", "skating", "social", "sports", "travel" ] -}, { - "name" : "panorama_horizontal_select", - "tags" : [ "angle", "horizontal", "image", "panorama", "photo", "photography", "picture", "select", "wide" ] -}, { - "name" : "border_horizontal", - "tags" : [ "border", "doc", "edit", "editing", "editor", "horizontal", "sheet", "spreadsheet", "stroke", "text", "type", "writing" ] -}, { - "name" : "2k", - "tags" : [ "2000", "2K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "wifi_1_bar", - "tags" : [ "1", "bar", "cell", "cellular", "connection", "data", "internet", "mobile", "network", "phone", "scan", "service", "signal", "wifi", "wireless" ] -}, { - "name" : "format_textdirection_r_to_l", - "tags" : [ "align", "alignment", "doc", "edit", "editing", "editor", "format", "rtl", "sheet", "spreadsheet", "text", "textdirection", "type", "writing" ] -}, { - "name" : "wifi_channel", - "tags" : [ "(scan)", "[cellular", "channel", "connection", "data", "internet", "mobile]", "network", "service", "signal", "wifi", "wireless" ] -}, { - "name" : "roundabout_right", - "tags" : [ "arrow", "arrows", "direction", "directions", "maps", "navigation", "path", "right", "roundabout", "route", "sign", "traffic" ] -}, { - "name" : "wb_auto", - "tags" : [ "A", "W", "alphabet", "auto", "automatic", "balance", "character", "edit", "editing", "font", "image", "letter", "photo", "photography", "symbol", "text", "type", "white", "wp" ] -}, { - "name" : "panorama_photosphere_select", - "tags" : [ "angle", "horizontal", "image", "panorama", "photo", "photography", "photosphere", "picture", "select", "wide" ] -}, { - "name" : "panorama_wide_angle", - "tags" : [ "angle", "image", "panorama", "photo", "photography", "picture", "wide" ] -}, { - "name" : "hdr_plus", - "tags" : [ "+", "add", "alphabet", "character", "circle", "dynamic", "enhance", "font", "hdr", "high", "letter", "plus", "range", "select", "symbol", "text", "type" ] -}, { - "name" : "panorama_vertical_select", - "tags" : [ "angle", "image", "panorama", "photo", "photography", "picture", "select", "vertical", "wide" ] -}, { - "name" : "border_top", - "tags" : [ "border", "doc", "edit", "editing", "editor", "sheet", "spreadsheet", "stroke", "text", "top", "type", "writing" ] -}, { - "name" : "mic_external_off", - "tags" : [ "audio", "disabled", "enabled", "external", "mic", "microphone", "off", "on", "slash", "sound", "voice" ] -}, { - "name" : "width_full", - "tags" : [ ] -}, { - "name" : "h_mobiledata", - "tags" : [ "alphabet", "character", "data", "font", "h", "letter", "mobile", "network", "service", "symbol", "text", "type" ] -}, { - "name" : "roller_shades", - "tags" : [ "blinds", "cover", "curtains", "nest", "open", "roller", "shade", "shutter", "sunshade" ] -}, { - "name" : "no_stroller", - "tags" : [ "baby", "care", "carriage", "child", "children", "disabled", "enabled", "infant", "kid", "newborn", "no", "off", "on", "parents", "slash", "stroller", "toddler", "young" ] -}, { - "name" : "tornado", - "tags" : [ "crisis", "disaster", "natural", "rain", "storm", "tornado", "weather", "wind" ] -}, { - "name" : "keyboard_control_key", - "tags" : [ "control key", "keyboard" ] -}, { - "name" : "turn_slight_right", - "tags" : [ "arrow", "arrows", "direction", "directions", "maps", "navigation", "path", "right", "route", "sharp", "sign", "slight", "traffic", "turn" ] -}, { - "name" : "border_right", - "tags" : [ "border", "doc", "edit", "editing", "editor", "right", "sheet", "spreadsheet", "stroke", "text", "type", "writing" ] -}, { - "name" : "1k_plus", - "tags" : [ "+", "1000", "1K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "plus", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "turn_slight_left", - "tags" : [ "arrow", "arrows", "direction", "directions", "maps", "navigation", "path", "right", "route", "sign", "slight", "traffic", "turn" ] -}, { - "name" : "screen_rotation_alt", - "tags" : [ "Android", "OS", "arrow", "device", "hardware", "iOS", "mobile", "phone", "rotate", "rotation", "screen", "tablet", "turn" ] -}, { - "name" : "dataset_linked", - "tags" : [ ] -}, { - "name" : "unfold_less_double", - "tags" : [ "arrow", "arrows", "chevron", "collapse", "direction", "double", "expand", "expandable", "inward", "less", "list", "navigation", "unfold", "up" ] -}, { - "name" : "8k", - "tags" : [ "8000", "8K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "landslide", - "tags" : [ "crisis", "disaster", "natural", "rain", "storm", "weather" ] -}, { - "name" : "media_bluetooth_off", - "tags" : [ "bluetooth", "connect", "connection", "connectivity", "device", "disabled", "enabled", "media", "music", "note", "off", "offline", "on", "paring", "signal", "slash", "symbol", "wireless" ] -}, { - "name" : "fire_truck", - "tags" : [ ] -}, { - "name" : "e_mobiledata", - "tags" : [ "alphabet", "data", "e", "font", "letter", "mobile", "mobiledata", "text", "type" ] -}, { - "name" : "panorama_vertical", - "tags" : [ "angle", "image", "panorama", "photo", "photography", "picture", "vertical", "wide" ] -}, { - "name" : "r_mobiledata", - "tags" : [ "alphabet", "character", "data", "font", "letter", "mobile", "r", "symbol", "text", "type" ] -}, { - "name" : "12mp", - "tags" : [ "12", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "repartition", - "tags" : [ "arrow", "arrows", "data", "partition", "refresh", "renew", "repartition", "restore", "table" ] -}, { - "name" : "width_normal", - "tags" : [ ] -}, { - "name" : "h_plus_mobiledata", - "tags" : [ "+", "alphabet", "character", "data", "font", "h", "letter", "mobile", "network", "plus", "service", "symbol", "text", "type" ] -}, { - "name" : "hdr_enhanced_select", - "tags" : [ "add", "alphabet", "character", "dynamic", "enhance", "font", "hdr", "high", "letter", "plus", "range", "select", "symbol", "text", "type" ] -}, { - "name" : "mp", - "tags" : [ "alphabet", "character", "font", "image", "letter", "megapixel", "mp", "photo", "photography", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "shape_line", - "tags" : [ "circle", "draw", "edit", "editing", "line", "shape", "square" ] -}, { - "name" : "9k_plus", - "tags" : [ "+", "9000", "9K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "plus", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "5k", - "tags" : [ "5000", "5K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "hevc", - "tags" : [ "alphabet", "character", "coding", "efficiency", "font", "hevc", "high", "letter", "symbol", "text", "type", "video" ] -}, { - "name" : "currency_franc", - "tags" : [ "bill", "card", "cash", "coin", "commerce", "cost", "credit", "currency", "dollars", "finance", "franc", "money", "online", "pay", "payment", "price", "shopping", "symbol" ] -}, { - "name" : "8k_plus", - "tags" : [ "+", "7000", "8K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "plus", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "hdr_on_select", - "tags" : [ "+", "alphabet", "camera", "character", "circle", "dynamic", "font", "hdr", "high", "letter", "on", "photo", "range", "select", "symbol", "text", "type" ] -}, { - "name" : "3k", - "tags" : [ "3000", "3K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "transcribe", - "tags" : [ ] -}, { - "name" : "width_wide", - "tags" : [ ] -}, { - "name" : "hdr_auto_select", - "tags" : [ "+", "A", "alphabet", "auto", "camera", "character", "circle", "dynamic", "font", "hdr", "high", "letter", "photo", "range", "select", "symbol", "text", "type" ] -}, { - "name" : "hls", - "tags" : [ "alphabet", "character", "develop", "developer", "engineer", "engineering", "font", "hls", "letter", "platform", "symbol", "text", "type" ] -}, { - "name" : "5k_plus", - "tags" : [ "+", "5000", "5K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "plus", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "assist_walker", - "tags" : [ "accessibility", "accessible", "assist", "body", "disability", "handicap", "help", "human", "injured", "injury", "mobility", "person", "walk", "walker" ] -}, { - "name" : "hls_off", - "tags" : [ "alphabet", "character", "develop", "developer", "disabled", "enabled", "engineer", "engineering", "font", "hls", "letter", "off", "offline", "on", "platform", "slash", "symbol", "text", "type" ] -}, { - "name" : "18mp", - "tags" : [ "18", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "format_overline", - "tags" : [ "alphabet", "character", "doc", "edit", "editing", "editor", "font", "format", "letter", "line", "overline", "sheet", "spreadsheet", "style", "symbol", "text", "type", "under", "writing" ] -}, { - "name" : "volcano", - "tags" : [ "crisis", "disaster", "eruption", "lava", "magma", "natural", "volcano" ] -}, { - "name" : "vaping_rooms", - "tags" : [ "allowed", "e-cigarette", "never", "no", "places", "prohibited", "smoke", "smoking", "tobacco", "vape", "vaping", "vapor", "warning", "zone" ] -}, { - "name" : "watch_off", - "tags" : [ "Android", "OS", "ar", "clock", "close", "gadget", "iOS", "off", "shut", "time", "vr", "watch", "wearables", "web", "wristwatch" ] -}, { - "name" : "9k", - "tags" : [ "9000", "9K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "23mp", - "tags" : [ "23", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "propane", - "tags" : [ "gas", "nest", "propane" ] -}, { - "name" : "raw_off", - "tags" : [ "alphabet", "character", "disabled", "enabled", "font", "image", "letter", "off", "on", "original", "photo", "photography", "raw", "slash", "symbol", "text", "type" ] -}, { - "name" : "keyboard_option_key", - "tags" : [ "alt key", "key", "keyboard", "modifier key", "option" ] -}, { - "name" : "woman_2", - "tags" : [ "female", "gender", "girl", "lady", "social", "symbol", "woman", "women" ] -}, { - "name" : "2k_plus", - "tags" : [ "+", "2k", "alphabet", "character", "digit", "font", "letter", "number", "plus", "symbol", "text", "type" ] -}, { - "name" : "6k_plus", - "tags" : [ "+", "6000", "6K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "plus", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "broadcast_on_personal", - "tags" : [ ] -}, { - "name" : "10mp", - "tags" : [ "10", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "man_2", - "tags" : [ "boy", "gender", "male", "man", "social", "symbol" ] -}, { - "name" : "7k", - "tags" : [ "7000", "7K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "7k_plus", - "tags" : [ "+", "7000", "7K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "plus", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "nearby_off", - "tags" : [ "disabled", "enabled", "nearby", "off", "on", "slash" ] -}, { - "name" : "3k_plus", - "tags" : [ "+", "3000", "3K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "plus", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "6k", - "tags" : [ "6000", "6K", "alphabet", "character", "digit", "display", "font", "letter", "number", "pixel", "pixels", "resolution", "symbol", "text", "type", "video" ] -}, { - "name" : "hdr_off", - "tags" : [ "alphabet", "character", "disabled", "dynamic", "enabled", "enhance", "font", "hdr", "high", "letter", "off", "on", "range", "select", "slash", "symbol", "text", "type" ] -}, { - "name" : "roundabout_left", - "tags" : [ "arrow", "arrows", "direction", "directions", "left", "maps", "navigation", "path", "roundabout", "route", "sign", "traffic" ] -}, { - "name" : "hdr_off_select", - "tags" : [ "alphabet", "camera", "character", "circle", "disabled", "dynamic", "enabled", "font", "hdr", "high", "letter", "off", "on", "photo", "range", "select", "slash", "symbol", "text", "type" ] -}, { - "name" : "bedtime_off", - "tags" : [ "bedtime", "disabled", "lunar", "moon", "night", "nightime", "off", "offline", "slash", "sleep" ] -}, { - "name" : "18_up_rating", - "tags" : [ ] -}, { - "name" : "turn_sharp_left", - "tags" : [ "arrow", "arrows", "direction", "directions", "left", "maps", "navigation", "path", "route", "sharp", "sign", "traffic", "turn" ] -}, { - "name" : "11mp", - "tags" : [ "11", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "roller_shades_closed", - "tags" : [ "blinds", "closed", "cover", "curtains", "nest", "roller", "shade", "shutter", "sunshade" ] -}, { - "name" : "20mp", - "tags" : [ "20", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "blinds", - "tags" : [ "blinds", "cover", "curtains", "nest", "open", "shade", "shutter", "sunshade" ] -}, { - "name" : "3mp", - "tags" : [ "3", "camera", "digit", "font", "image", "letters", "megapixel", "megapixels", "mp", "number", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "blind", - "tags" : [ "accessibility", "accessible", "assist", "blind", "body", "cane", "disability", "handicap", "help", "human", "mobility", "person", "walk", "walker" ] -}, { - "name" : "emergency_recording", - "tags" : [ "alert", "attention", "camera", "caution", "danger", "emergency", "film", "filming", "hardware", "image", "important", "motion", "notification", "picture", "record", "video", "videography", "warning" ] -}, { - "name" : "curtains", - "tags" : [ "blinds", "cover", "curtains", "nest", "open", "shade", "shutter", "sunshade" ] -}, { - "name" : "13mp", - "tags" : [ "13", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "5mp", - "tags" : [ "5", "camera", "digit", "font", "image", "letters", "megapixel", "megapixels", "mp", "number", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "21mp", - "tags" : [ "21", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "blinds_closed", - "tags" : [ "blinds", "closed", "cover", "curtains", "nest", "shade", "shutter", "sunshade" ] -}, { - "name" : "16mp", - "tags" : [ "16", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "17mp", - "tags" : [ "17", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "2mp", - "tags" : [ "2", "camera", "digit", "font", "image", "letters", "megapixel", "megapixels", "mp", "number", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "15mp", - "tags" : [ "15", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "desk", - "tags" : [ ] -}, { - "name" : "no_adult_content", - "tags" : [ ] -}, { - "name" : "14mp", - "tags" : [ "14", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "22mp", - "tags" : [ "22", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "vertical_shades", - "tags" : [ "blinds", "cover", "curtains", "nest", "open", "shade", "shutter", "sunshade", "vertical" ] -}, { - "name" : "vertical_shades_closed", - "tags" : [ "blinds", "closed", "cover", "curtains", "nest", "roller", "shade", "shutter", "sunshade" ] -}, { - "name" : "curtains_closed", - "tags" : [ "blinds", "closed", "cover", "curtains", "nest", "shade", "shutter", "sunshade" ] -}, { - "name" : "broadcast_on_home", - "tags" : [ ] -}, { - "name" : "4mp", - "tags" : [ "4", "camera", "digit", "font", "image", "letters", "megapixel", "megapixels", "mp", "number", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "19mp", - "tags" : [ "19", "camera", "digits", "font", "image", "letters", "megapixel", "megapixels", "mp", "numbers", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "nest_cam_wired_stand", - "tags" : [ "camera", "film", "filming", "hardware", "image", "motion", "nest", "picture", "stand", "video", "videography", "wired" ] -}, { - "name" : "9mp", - "tags" : [ "9", "camera", "digit", "font", "image", "letters", "megapixel", "megapixels", "mp", "number", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "7mp", - "tags" : [ "7", "camera", "digit", "font", "image", "letters", "megapixel", "megapixels", "mp", "number", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "8mp", - "tags" : [ "8", "camera", "digit", "font", "image", "letters", "megapixel", "megapixels", "mp", "number", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "6mp", - "tags" : [ "6", "camera", "digit", "font", "image", "letters", "megapixel", "megapixels", "mp", "number", "pixel", "pixels", "quality", "resolution", "symbol", "text", "type" ] -}, { - "name" : "devices_fold", - "tags" : [ "Android", "OS", "cell", "device", "fold", "foldable", "hardware", "iOS", "mobile", "phone", "tablet" ] -}, { - "name" : "vape_free", - "tags" : [ "disabled", "e-cigarette", "enabled", "free", "never", "no", "off", "on", "places", "prohibited", "slash", "smoke", "smoking", "tobacco", "vape", "vaping", "vapor", "warning", "zone" ] -}, { - "name" : "ramp_left", - "tags" : [ "arrow", "arrows", "direction", "directions", "left", "maps", "navigation", "path", "ramp", "route", "sign", "traffic" ] -}, { - "name" : "ramp_right", - "tags" : [ "arrow", "arrows", "direction", "directions", "maps", "navigation", "path", "ramp", "right", "route", "sign", "traffic" ] -}, { - "name" : "video_chat", - "tags" : [ "bubble", "cam", "camera", "chat", "comment", "communicate", "facetime", "feedback", "message", "speech", "video", "voice" ] -}, { - "name" : "type_specimen", - "tags" : [ ] -}, { - "name" : "man_4", - "tags" : [ "abstract", "boy", "gender", "male", "man", "social", "symbol" ] -}, { - "name" : "fluorescent", - "tags" : [ "bright", "fluorescent", "lamp", "light", "lightbulb" ] -}, { - "name" : "man_3", - "tags" : [ "abstract", "boy", "gender", "male", "man", "social", "symbol" ] -}, { - "name" : "fire_hydrant_alt", - "tags" : [ ] -}, { - "name" : "macro_off", - "tags" : [ "camera", "disabled", "enabled", "flower", "garden", "image", "macro", "off", "offline", "on", "slash" ] -} ] \ No newline at end of file +[{"name":"more_horiz","tags":["3","DISABLE_IOS","app","application","components","disable_ios","dots","etc","horiz","horizontal","interface","ios","more","screen","site","three","ui","ux","web","website"]},{"name":"more_vert","tags":["3","DISABLE_IOS","android","app","application","components","disable_ios","dots","etc","interface","more","screen","site","three","ui","ux","vert","vertical","web","website"]},{"name":"open_in_new","tags":["app","application","arrow","box","components","in","interface","new","open","right","screen","site","ui","up","ux","web","website","window"]},{"name":"visibility","tags":["eye","on","reveal","see","show","view","visibility"]},{"name":"play_arrow","tags":["arrow","control","controls","media","music","play","video"]},{"name":"arrow_back","tags":["DISABLE_IOS","app","application","arrow","back","components","direction","disable_ios","interface","left","navigation","previous","screen","site","ui","ux","web","website"]},{"name":"arrow_downward","tags":["app","application","arrow","components","direction","down","downward","interface","navigation","screen","site","ui","ux","web","website"]},{"name":"arrow_forward","tags":["app","application","arrow","arrows","components","direction","forward","interface","navigation","right","screen","site","ui","ux","web","website"]},{"name":"arrow_upward","tags":["app","application","arrow","components","direction","interface","navigation","screen","site","ui","up","upward","ux","web","website"]},{"name":"close","tags":["cancel","close","exit","stop","x"]},{"name":"refresh","tags":["around","arrow","arrows","direction","inprogress","load","loading refresh","navigation","refresh","renew","right","rotate","turn"]},{"name":"menu","tags":["app","application","components","hamburger","interface","line","lines","menu","screen","site","ui","ux","web","website"]},{"name":"show_chart","tags":["analytics","bar","bars","chart","data","diagram","graph","infographic","line","measure","metrics","presentation","show chart","statistics","tracking"]},{"name":"multiline_chart","tags":["analytics","bar","bars","chart","data","diagram","graph","infographic","line","measure","metrics","multiple","statistics","tracking"]},{"name":"pie_chart","tags":["analytics","bar","bars","chart","data","diagram","graph","infographic","measure","metrics","pie","statistics","tracking"]},{"name":"insert_chart","tags":["analytics","bar","bars","chart","data","diagram","graph","infographic","insert","measure","metrics","statistics","tracking"]},{"name":"people","tags":["accounts","committee","face","family","friends","humans","network","people","persons","profiles","social","team","users"]},{"name":"person","tags":["account","face","human","people","person","profile","user"]},{"name":"domain","tags":["apartment","architecture","building","business","domain","estate","home","place","real","residence","residential","shelter","web","www"]},{"name":"devices_other","tags":["Android","OS","ar","cell","chrome","desktop","device","gadget","hardware","iOS","ipad","mac","mobile","monitor","other","phone","tablet","vr","watch","wearables","window"]},{"name":"widgets","tags":["app","box","menu","setting","squares","ui","widgets"]},{"name":"dashboard","tags":["cards","dashboard","format","layout","rectangle","shapes","square","web","website"]},{"name":"map","tags":["destination","direction","location","map","maps","pin","place","route","stop","travel"]},{"name":"pin_drop","tags":["destination","direction","drop","location","maps","navigation","pin","place","stop"]},{"name":"gps_fixed","tags":["destination","direction","fixed","gps","location","maps","pin","place","pointer","stop","tracking"]},{"name":"extension","tags":["app","extended","extension","game","jigsaw","plugin add","puzzle","shape"]},{"name":"search","tags":["filter","find","glass","look","magnify","magnifying","search","see"]},{"name":"settings","tags":["application","change","details","gear","info","information","options","personal","service","settings"]},{"name":"notifications","tags":["active","alarm","alert","bell","chime","notifications","notify","reminder","ring","sound"]},{"name":"notifications_active","tags":["active","alarm","alert","bell","chime","notifications","notify","reminder","ring","ringing","sound"]},{"name":"info","tags":["alert","announcement","assistance","details","help","i","info","information","service","support"]},{"name":"error_outline","tags":["!","alert","attention","caution","circle","danger","error","exclamation","important","mark","notification","outline","symbol","warning"]},{"name":"warning","tags":["!","alert","attention","caution","danger","error","exclamation","important","mark","notification","symbol","triangle","warning"]},{"name":"list","tags":["file","format","index","list","menu","options"]},{"name":"download","tags":["arrow","down","download","downloads","drive","install","upload"]},{"name":"import_export","tags":["arrow","arrows","direction","down","explort","import","up"]},{"name":"share","tags":["DISABLE_IOS","android","connect","contect","disable_ios","link","media","multimedia","multiple","network","options","share","shared","sharing","social"]},{"name":"add","tags":["+","add","new symbol","plus","symbol"]},{"name":"edit","tags":["compose","create","edit","editing","input","new","pen","pencil","write","writing"]},{"name":"check","tags":["DISABLE_IOS","check","confirm","correct","disable_ios","done","enter","mark","ok","okay","select","tick","yes"]},{"name":"delete","tags":["bin","can","delete","garbage","remove","trash"]},{"name":"thermostat","tags":["climate","forecast","temperature","thermostat","weather"]},{"name":"air","tags":["air","blowing","breeze","flow","wave","weather","wind"]},{"name":"lightbulb","tags":["alert","announcement","idea","info","information","light","lightbulb"]},{"name":"home","tags":["address","app","application--house","architecture","building","components","design","estate","home","interface","layout","place","real","residence","residential","screen","shelter","site","structure","ui","unit","ux","web","website","window"]},{"name":"account_circle","tags":["account","avatar","circle","face","human","people","person","profile","thumbnail","user"]},{"name":"done","tags":["DISABLE_IOS","approve","check","complete","disable_ios","done","mark","ok","select","tick","validate","verified","yes"]},{"name":"check_circle","tags":["approve","check","circle","complete","done","mark","ok","select","tick","validate","verified","yes"]},{"name":"expand_more","tags":["arrow","arrows","chevron","collapse","direction","down","expand","expandable","list","more"]},{"name":"shopping_cart","tags":["add","bill","buy","card","cart","cash","checkout","coin","commerce","credit","currency","dollars","money","online","pay","payment","shopping"]},{"name":"email","tags":["email","envelop","letter","mail","message","send"]},{"name":"favorite","tags":["appreciate","favorite","heart","like","love","remember","save","shape"]},{"name":"description","tags":["article","data","description","doc","document","drive","file","folder","folders","notes","page","paper","sheet","slide","text","writing"]},{"name":"logout","tags":["app","application","arrow","components","design","exit","interface","leave","log","login","logout","right","screen","site","ui","ux","web","website"]},{"name":"favorite_border","tags":["border","favorite","heart","like","love","outline","remember","save","shape"]},{"name":"chevron_right","tags":["arrow","arrows","chevron","direction","right"]},{"name":"lock","tags":["lock","locked","password","privacy","private","protection","safety","secure","security"]},{"name":"location_on","tags":["destination","direction","location","maps","on","pin","place","room","stop"]},{"name":"schedule","tags":["clock","date","schedule","time"]},{"name":"local_shipping","tags":["automobile","car","cars","delivery","letter","local","mail","maps","office","package","parcel","post","postal","send","shipping","shopping","stamp","transportation","truck","vehicle"]},{"name":"language","tags":["globe","internet","language","planet","website","world","www"]},{"name":"call","tags":["call","cell","contact","device","hardware","mobile","phone","telephone"]},{"name":"file_download","tags":["arrow","arrows","down","download","downloads","drive","export","file","install","upload"]},{"name":"arrow_forward_ios","tags":["app","application","arrow","chevron","components","direction","forward","interface","ios","navigation","next","right","screen","site","ui","ux","web","website"]},{"name":"arrow_back_ios","tags":["DISABLE_IOS","app","application","arrow","back","chevron","components","direction","disable_ios","interface","ios","left","navigation","previous","screen","site","ui","ux","web","website"]},{"name":"groups","tags":["body","club","collaboration","crowd","gathering","groups","human","meeting","people","person","social","teams"]},{"name":"cancel","tags":["cancel","circle","close","exit","stop","x"]},{"name":"help_outline","tags":["?","assistance","circle","help","info","information","outline","punctuation","question mark","recent","restore","shape","support","symbol"]},{"name":"arrow_drop_down","tags":["app","application","arrow","components","direction","down","drop","interface","navigation","screen","site","ui","ux","web","website"]},{"name":"face","tags":["account","emoji","eyes","face","human","lock","log","login","logout","people","person","profile","recognition","security","social","thumbnail","unlock","user"]},{"name":"manage_accounts","tags":["accounts","change","details service-human","face","gear","manage","options","people","person","profile","settings","user"]},{"name":"place","tags":["destination","direction","location","maps","navigation","pin","place","point","stop"]},{"name":"verified","tags":["approve","badge","burst","check","complete","done","mark","ok","select","star","tick","validate","verified","yes"]},{"name":"add_circle_outline","tags":["+","add","circle","create","new","outline","plus"]},{"name":"filter_alt","tags":["alt","edit","filter","funnel","options","refine","sift"]},{"name":"thumb_up","tags":["favorite","fingers","gesture","hand","hands","like","rank","ranking","rate","rating","thumb","up"]},{"name":"event","tags":["calendar","date","day","event","mark","month","range","remember","reminder","today","week"]},{"name":"star","tags":["best","bookmark","favorite","highlight","ranking","rate","rating","save","star","toggle"]},{"name":"fingerprint","tags":["finger","fingerprint","id","identification","identity","print","reader","thumbprint","verification"]},{"name":"content_copy","tags":["content","copy","cut","doc","document","duplicate","file","multiple","past"]},{"name":"login","tags":["access","app","application","arrow","components","design","enter","in","interface","left","log","login","screen","sign","site","ui","ux","web","website"]},{"name":"add_circle","tags":["+","add","circle","create","new","plus"]},{"name":"visibility_off","tags":["disabled","enabled","eye","off","on","reveal","see","show","slash","view","visibility"]},{"name":"check_circle_outline","tags":["approve","check","circle","complete","done","finished","mark","ok","outline","select","tick","validate","verified","yes"]},{"name":"chevron_left","tags":["DISABLE_IOS","arrow","arrows","chevron","direction","disable_ios","left"]},{"name":"calendar_today","tags":["calendar","date","day","event","month","schedule","today"]},{"name":"send","tags":["email","mail","message","paper","plane","reply","right","send","share"]},{"name":"check_box","tags":["approved","box","button","check","component","control","form","mark","ok","select","selected","selection","tick","toggle","ui","yes"]},{"name":"highlight_off","tags":["cancel","close","exit","highlight","no","off","quit","remove","stop","x"]},{"name":"navigate_next","tags":["arrow","arrows","direction","navigate","next","right"]},{"name":"help","tags":["?","assistance","circle","help","info","information","punctuation","question mark","recent","restore","shape","support","symbol"]},{"name":"phone","tags":["call","cell","contact","device","hardware","mobile","phone","telephone"]},{"name":"paid","tags":["circle","currency","money","paid","payment","transaction"]},{"name":"task_alt","tags":["approve","check","circle","complete","done","mark","ok","select","task","tick","validate","verified","yes"]},{"name":"question_answer","tags":["answer","bubble","chat","comment","communicate","conversation","feedback","message","question","speech","talk"]},{"name":"expand_less","tags":["arrow","arrows","chevron","collapse","direction","expand","expandable","less","list","up"]},{"name":"clear","tags":["back","cancel","clear","correct","delete","erase","exit","x"]},{"name":"date_range","tags":["calendar","date","day","event","month","range","remember","reminder","schedule","time","today","week"]},{"name":"article","tags":["article","doc","document","file","page","paper","text","writing"]},{"name":"error","tags":["!","alert","attention","caution","circle","danger","error","exclamation","important","mark","notification","symbol","warning"]},{"name":"photo_camera","tags":["camera","image","photo","photography","picture"]},{"name":"check_box_outline_blank","tags":["blank","box","button","check","component","control","deselected","empty","form","outline","select","selection","square","tick","toggle","ui"]},{"name":"image","tags":["disabled","enabled","hide","image","landscape","mountain","mountains","off","on","photo","photography","picture","slash"]},{"name":"shopping_bag","tags":["bag","bill","business","buy","card","cart","cash","coin","commerce","credit","currency","dollars","money","online","pay","payment","shop","shopping","store","storefront"]},{"name":"person_outline","tags":["account","face","human","outline","people","person","profile","user"]},{"name":"school","tags":["academy","achievement","cap","class","college","education","graduation","hat","knowledge","learning","school","university"]},{"name":"file_upload","tags":["arrow","arrows","download","drive","export","file","up","upload"]},{"name":"perm_identity","tags":["account","avatar","face","human","identity","people","perm","person","profile","thumbnail","user"]},{"name":"credit_card","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","money","online","pay","payment","price","shopping","symbol"]},{"name":"history","tags":["arrow","back","backwards","clock","date","history","refresh","renew","reverse","rotate","schedule","time","turn"]},{"name":"trending_up","tags":["analytics","arrow","data","diagram","graph","infographic","measure","metrics","movement","rate","rating","statistics","tracking","trending","up"]},{"name":"support_agent","tags":["agent","care","customer","face","headphone","person","representative","service","support"]},{"name":"account_balance","tags":["account","balance","bank","bill","card","cash","coin","commerce","credit","currency","dollars","finance","money","online","pay","payment"]},{"name":"delete_outline","tags":["bin","can","delete","garbage","outline","remove","trash"]},{"name":"attach_money","tags":["attach","attachment","bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","money","online","pay","payment","symbol"]},{"name":"person_add","tags":["+","account","add","avatar","face","human","new","people","person","plus","profile","symbol","user"]},{"name":"public","tags":["earth","global","globe","map","network","planet","public","social","space","web","world"]},{"name":"save","tags":["data","disk","document","drive","file","floppy","multimedia","save","storage"]},{"name":"mail","tags":["email","envelop","letter","mail","message","send"]},{"name":"report_problem","tags":["!","alert","attention","caution","danger","error","exclamation","feedback","important","mark","notification","problem","report","symbol","triangle","warning"]},{"name":"fact_check","tags":["approve","check","complete","done","fact","list","mark","ok","select","tick","validate","verified","yes"]},{"name":"radio_button_unchecked","tags":["bullet","button","circle","deselected","form","off","on","point","radio","record","select","toggle","unchecked"]},{"name":"verified_user","tags":["approve","certified","check","complete","done","mark","ok","privacy","private","protect","protection","security","select","shield","tick","user","validate","verified","yes"]},{"name":"assignment","tags":["assignment","clipboard","doc","document","text","writing"]},{"name":"link","tags":["chain","clip","connection","link","linked","links","multimedia","url"]},{"name":"play_circle_filled","tags":["arrow","circle","control","controls","media","music","play","video"]},{"name":"emoji_events","tags":["achievement","award","chalice","champion","cup","emoji","events","first","prize","reward","sport","trophy","winner"]},{"name":"remove","tags":["can","delete","minus","negative","remove","substract","trash"]},{"name":"star_rate","tags":["achievement","bookmark","favorite","highlight","important","marked","ranking","rate","rating rank","reward","save","saved","shape","special","star"]},{"name":"apps","tags":["all","applications","apps","circles","collection","components","dots","grid","interface","squares","ui","ux"]},{"name":"business","tags":["apartment","architecture","building","business","company","estate","home","place","real","residence","residential","shelter"]},{"name":"filter_list","tags":["filter","lines","list","organize","sort"]},{"name":"arrow_right_alt","tags":["alt","arrow","arrows","direction","east","navigation","pointing","right"]},{"name":"chat","tags":["bubble","chat","comment","communicate","feedback","message","speech"]},{"name":"account_balance_wallet","tags":["account","balance","bank","bill","card","cash","coin","commerce","credit","currency","dollars","finance","money","online","pay","payment","wallet"]},{"name":"payments","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","layer","money","multiple","online","pay","payment","payments","price","shopping","symbol"]},{"name":"menu_book","tags":["book","dining","food","meal","menu","restaurant"]},{"name":"folder","tags":["data","doc","document","drive","file","folder","folders","sheet","slide","storage"]},{"name":"keyboard_arrow_down","tags":["arrow","arrows","down","keyboard"]},{"name":"autorenew","tags":["around","arrow","arrows","autorenew","cache","cached","direction","inprogress","load","loading refresh","navigation","renew","rotate","turn"]},{"name":"build","tags":["adjust","build","fix","home","nest","repair","tool","tools","wrench"]},{"name":"videocam","tags":["cam","camera","conference","film","filming","hardware","image","motion","picture","video","videography"]},{"name":"view_list","tags":["design","format","grid","layout","lines","list","stacked","view","website"]},{"name":"print","tags":["draft","fax","ink","machine","office","paper","print","printer","send"]},{"name":"work","tags":["bag","baggage","briefcase","business","case","job","suitcase","work"]},{"name":"store","tags":["bill","building","business","card","cash","coin","commerce","company","credit","currency","dollars","market","money","online","pay","payment","shop","shopping","store","storefront"]},{"name":"analytics","tags":["analytics","assessment","bar","chart","data","diagram","graph","infographic","measure","metrics","statistics","tracking"]},{"name":"radio_button_checked","tags":["app","application","bullet","button","checked","circle","components","design","form","interface","off","on","point","radio","record","screen","select","selected","site","toggle","ui","ux","web","website"]},{"name":"phone_iphone","tags":["Android","OS","cell","device","hardware","iOS","iphone","mobile","phone","tablet"]},{"name":"play_circle","tags":["arrow","circle","control","controls","media","music","play","video"]},{"name":"tune","tags":["adjust","audio","controls","custom","customize","edit","editing","filter","filters","instant","mix","music","options","setting","settings","slider","sliders","switches","tune"]},{"name":"delete_forever","tags":["bin","can","cancel","delete","exit","forever","garbage","remove","trash","x"]},{"name":"today","tags":["calendar","date","day","event","mark","month","remember","reminder","schedule","time","today"]},{"name":"grid_view","tags":["app","application square","blocks","components","dashboard","design","grid","interface","layout","screen","site","tiles","ui","ux","view","web","website","window"]},{"name":"east","tags":["arrow","directional","east","maps","navigation","right"]},{"name":"inventory_2","tags":["archive","box","file","inventory","organize","packages","product","stock","storage","supply"]},{"name":"mail_outline","tags":["email","envelop","letter","mail","message","outline","send"]},{"name":"admin_panel_settings","tags":["account","admin","avatar","certified","face","human","panel","people","person","privacy","private","profile","protect","protection","security","settings","shield","user","verified"]},{"name":"mic","tags":["hear","hearing","mic","microphone","noise","record","sound","voice"]},{"name":"calendar_month","tags":["calendar","date","day","event","month","schedule","today"]},{"name":"group","tags":["accounts","committee","face","family","friends","group","humans","network","people","persons","profiles","social","team","users"]},{"name":"picture_as_pdf","tags":["alphabet","as","character","document","file","font","image","letter","multiple","pdf","photo","photography","picture","symbol","text","type"]},{"name":"lock_open","tags":["lock","open","password","privacy","private","protection","safety","secure","security","unlocked"]},{"name":"volume_up","tags":["audio","control","music","sound","speaker","tv","up","volume"]},{"name":"watch_later","tags":["clock","date","later","schedule","time","watch"]},{"name":"grade","tags":["'favorite_news' .","'star_outline'","Duplicate of 'star_boarder'","star_border_purple500'"]},{"name":"receipt_long","tags":["bill","check","document","list","long","paper","paperwork","receipt","record","store","transaction"]},{"name":"local_offer","tags":["deal","discount","offer","price","shop","shopping","store","tag"]},{"name":"room","tags":["destination","direction","location","maps","pin","place","room","stop"]},{"name":"update","tags":["arrow","back","backwards","clock","forward","history","load","refresh","reverse","schedule","time","update"]},{"name":"badge","tags":["account","avatar","badge","card","certified","employee","face","human","identification","name","people","person","profile","security","user","work"]},{"name":"savings","tags":["bank","bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","money","online","pay","payment","pig","piggy","savings","symbol"]},{"name":"code","tags":["brackets","code","css","develop","developer","engineer","engineering","html","platform"]},{"name":"light_mode","tags":["bright","brightness","day","device","light","lighting","mode","morning","sky","sun","sunny"]},{"name":"receipt","tags":[]},{"name":"circle","tags":["circle","full","geometry","moon"]},{"name":"inventory","tags":["archive","box","clipboard","doc","document","file","inventory","organize","packages","product","stock","supply"]},{"name":"add_shopping_cart","tags":["add","card","cart","cash","checkout","coin","commerce","credit","currency","dollars","money","online","pay","payment","plus","shopping"]},{"name":"contact_support","tags":["?","bubble","chat","comment","communicate","contact","help","info","information","mark","message","punctuation","question","question mark","speech","support","symbol"]},{"name":"category","tags":["categories","category","circle","collection","items","product","sort","square","triangle"]},{"name":"edit_note","tags":["compose","create","draft","edit","editing","input","lines","note","pen","pencil","text","write","writing"]},{"name":"insights","tags":["ai","analytics","artificial","automatic","automation","bar","bars","chart","custom","data","diagram","genai","graph","infographic","insights","intelligence","magic","measure","metrics","smart","spark","sparkle","star","stars","statistics","tracking"]},{"name":"power_settings_new","tags":["info","information","off","on","power","save","settings","shutdown"]},{"name":"campaign","tags":["alert","announcement","campaign","loud","megaphone","microphone","notification","speaker"]},{"name":"format_list_bulleted","tags":["align","alignment","bulleted","doc","edit","editing","editor","format","list","notes","sheet","spreadsheet","text","type","writing"]},{"name":"star_border","tags":["best","bookmark","border","favorite","highlight","outline","ranking","rate","rating","save","star","toggle"]},{"name":"pause","tags":["control","controls","media","music","pause","video"]},{"name":"remove_circle_outline","tags":["block","can","circle","delete","minus","negative","outline","remove","substract","trash"]},{"name":"warning_amber","tags":["!","alert","amber","attention","caution","danger","error","exclamation","important","mark","notification","symbol","triangle","warning"]},{"name":"wifi","tags":["connection","data","internet","network","scan","service","signal","wifi","wireless"]},{"name":"arrow_back_ios_new","tags":["DISABLE_IOS","app","application","arrow","back","chevron","components","direction","disable_ios","interface","ios","left","navigation","new","previous","screen","site","ui","ux","web","website"]},{"name":"restart_alt","tags":["alt","around","arrow","inprogress","load","loading refresh","reboot","renew","repeat","reset","restart"]},{"name":"done_all","tags":["all","approve","check","complete","done","layers","mark","multiple","ok","select","stack","tick","validate","verified","yes"]},{"name":"pets","tags":["animal","cat","dog","hand","paw","pet"]},{"name":"storefront","tags":["business","buy","cafe","commerce","front","market","places","restaurant","retail","sell","shop","shopping","store","storefront"]},{"name":"sort","tags":["filter","find","lines","list","organize","sort"]},{"name":"mode_edit","tags":["compose","create","draft","draw","edit","mode","pen","pencil","write"]},{"name":"list_alt","tags":["alt","box","contained","format","lines","list","order","reorder","stacked","title"]},{"name":"toggle_on","tags":["active","app","application","components","configuration","control","design","disable","inable","inactive","interface","off","on","selection","settings","site","slider","switch","toggle","ui","ux","web","website"]},{"name":"dark_mode","tags":["app","application","dark","device","interface","mode","moon","night","silent","theme","ui","ux","website"]},{"name":"engineering","tags":["body","cogs","cogwheel","construction","engineering","fixing","gears","hat","helmet","human","maintenance","people","person","setting","worker"]},{"name":"explore","tags":["compass","destination","direction","east","explore","location","maps","needle","north","south","travel","west"]},{"name":"bolt","tags":["bolt","electric","energy","fast","flash","lightning","power","thunderbolt"]},{"name":"construction","tags":["build","carpenter","construction","equipment","fix","hammer","improvement","industrial","industry","repair","tools","wrench"]},{"name":"qr_code_scanner","tags":["barcode","camera","code","media","product","qr","quick","response","scanner","smartphone","url","urls"]},{"name":"bookmark","tags":["archive","bookmark","favorite","label","library","read","reading","remember","ribbon","save","tag"]},{"name":"vpn_key","tags":["code","key","lock","network","passcode","password","unlock","vpn"]},{"name":"monetization_on","tags":["bill","card","cash","circle","coin","commerce","cost","credit","currency","dollars","finance","monetization","money","on","online","pay","payment","shopping","symbol"]},{"name":"attach_file","tags":["add","attach","attachment","clip","file","link","mail","media"]},{"name":"timer","tags":["alarm","alert","bell","clock","disabled","duration","enabled","notification","off","on","slash","stop","time","timer","watch"]},{"name":"account_box","tags":["account","avatar","box","face","human","people","person","profile","square","thumbnail","user"]},{"name":"note_add","tags":["+","-doc","add","data","document","drive","file","folder","folders","new","note","page","paper","plus","sheet","slide","symbol","writing"]},{"name":"reorder","tags":["format","lines","list","order","reorder","stacked"]},{"name":"bookmark_border","tags":["archive","bookmark","border","favorite","label","library","read","reading","remember","ribbon","save","tag"]},{"name":"arrow_right","tags":["app","application","arrow","components","direction","interface","navigation","right","screen","site","ui","ux","web","website"]},{"name":"pending_actions","tags":["actions","clipboard","clock","date","doc","document","pending","remember","schedule","time"]},{"name":"smartphone","tags":["Android","OS","call","cell","chat","device","hardware","iOS","mobile","phone","smartphone","tablet","text"]},{"name":"upload_file","tags":["arrow","data","doc","document","download","drive","file","folder","folders","page","paper","sheet","slide","up","upload","writing"]},{"name":"account_tree","tags":["account","analytics","chart","connect","data","diagram","flow","graph","infographic","measure","metrics","process","square","statistics","structure","tracking","tree"]},{"name":"shopping_basket","tags":["add","basket","bill","buy","card","cart","cash","checkout","coin","commerce","credit","currency","dollars","money","online","pay","payment","shopping"]},{"name":"flag","tags":["country","flag","goal","mark","nation","report","start"]},{"name":"apartment","tags":["accommodation","apartment","architecture","building","city","company","estate","flat","home","house","office","places","real","residence","residential","shelter","units","workplace"]},{"name":"restaurant","tags":["breakfast","dining","dinner","eat","food","fork","knife","local","lunch","meal","places","restaurant","spoon","utensils"]},{"name":"people_alt","tags":["accounts","committee","face","family","friends","humans","network","people","persons","profiles","social","team","users"]},{"name":"reply","tags":["arrow","backward","left","mail","message","reply","send","share"]},{"name":"play_circle_outline","tags":["arrow","circle","control","controls","media","music","outline","play","video"]},{"name":"payment","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","money","online","pay","payment","price","shopping","symbol"]},{"name":"sync","tags":["360","around","arrow","arrows","direction","inprogress","load","loading refresh","renew","rotate","sync","turn"]},{"name":"task","tags":["approve","check","complete","data","doc","document","done","drive","file","folder","folders","mark","ok","page","paper","select","sheet","slide","task","tick","validate","verified","writing","yes"]},{"name":"launch","tags":["app","application","arrow","box","components","interface","launch","new","open","screen","site","ui","ux","web","website","window"]},{"name":"menu_open","tags":["app","application","arrow","components","hamburger","interface","left","line","lines","menu","open","screen","site","ui","ux","web","website"]},{"name":"add_box","tags":["add","box","new square","plus","symbol"]},{"name":"drag_indicator","tags":["app","application","circles","components","design","dots","drag","drop","indicator","interface","layout","mobile","monitor","move","phone","screen","shape","shift","site","tablet","ui","ux","web","website","window"]},{"name":"supervisor_account","tags":["account","avatar","control","face","human","parental","parental control","parents","people","person","profile","supervised","supervisor","user"]},{"name":"touch_app","tags":["app","command","fingers","gesture","hand","press","tap","touch"]},{"name":"pending","tags":["circle","dots","loading","pending","progress","wait","waiting"]},{"name":"zoom_in","tags":["big","bigger","find","glass","grow","in","look","magnify","magnifying","plus","scale","search","see","size","zoom"]},{"name":"manage_search","tags":["glass","history","magnifying","manage","search","text"]},{"name":"remove_circle","tags":["block","can","circle","delete","minus","negative","remove","substract","trash"]},{"name":"group_add","tags":["accounts","add","committee","face","family","friends","group","humans","increase","more","network","people","persons","plus","profiles","social","team","users"]},{"name":"chat_bubble_outline","tags":["bubble","chat","comment","communicate","feedback","message","outline","speech"]},{"name":"assessment","tags":["analytics","assessment","bar","chart","data","diagram","graph","infographic","measure","metrics","statistics","tracking"]},{"name":"priority_high","tags":["!","alert","attention","caution","danger","error","exclamation","high","important","mark","notification","symbol","warning"]},{"name":"push_pin","tags":["location","marker","pin","place","push","remember","save"]},{"name":"feed","tags":["article","feed","headline","information","news","newspaper","paper","public","social","timeline"]},{"name":"leaderboard","tags":["analytics","bar","bars","chart","data","diagram","graph","infographic","leaderboard","measure","metrics","statistics","tracking"]},{"name":"summarize","tags":["doc","document","list","menu","note","report","summary"]},{"name":"block","tags":["avoid","block","cancel","close","entry","exit","no","prohibited","quit","remove","stop"]},{"name":"event_available","tags":["approve","available","calendar","check","complete","date","done","event","mark","ok","schedule","select","tick","time","validate","verified","yes"]},{"name":"thumb_up_off_alt","tags":["alt","disabled","enabled","favorite","fingers","gesture","hand","hands","like","off","offline","on","rank","ranking","rate","rating","slash","thumb","up"]},{"name":"directions_car","tags":["automobile","car","cars","direction","directions","maps","public","transportation","vehicle"]},{"name":"open_in_full","tags":["action","arrow","arrows","expand","full","grow","in","move","open"]},{"name":"auto_stories","tags":["auto","book","flipping","pages","stories"]},{"name":"post_add","tags":["+","add","data","doc","document","drive","file","folder","folders","page","paper","plus","post","sheet","slide","text","writing"]},{"name":"calculate","tags":["+","-","=","calculate","count","finance calculator","math"]},{"name":"alternate_email","tags":["@","address","alternate","contact","email","tag"]},{"name":"create","tags":["compose","create","edit","editing","input","new","pen","pencil","write","writing"]},{"name":"cloud_upload","tags":["app","application","arrow","backup","cloud","connection","download","drive","files","folders","internet","network","sky","storage","up","upload"]},{"name":"local_fire_department","tags":["911","climate","department","fire","firefighter","flame","heat","home","hot","nest","thermostat"]},{"name":"bar_chart","tags":["analytics","bar","chart","data","diagram","graph","infographic","measure","metrics","statistics","tracking"]},{"name":"password","tags":["key","login","password","pin","security","star","unlock"]},{"name":"collections","tags":["album","collections","gallery","image","landscape","library","mountain","mountains","photo","photography","picture","stack"]},{"name":"preview","tags":["design","eye","layout","preview","reveal","screen","see","show","site","view","web","website","window","www"]},{"name":"star_outline","tags":["bookmark","favorite","half","highlight","ranking","rate","rating","save","star","toggle"]},{"name":"exit_to_app","tags":["app","application","arrow","components","design","exit","export","interface","layout","leave","mobile","monitor","move","output","phone","screen","site","tablet","to","ui","ux","web","website","window"]},{"name":"done_outline","tags":["all","approve","check","complete","done","mark","ok","outline","select","tick","validate","verified","yes"]},{"name":"psychology","tags":["behavior","body","brain","cognitive","function","gear","head","human","intellectual","mental","mind","people","person","preferences","psychiatric","psychology","science","settings","social","therapy","thinking","thoughts"]},{"name":"assignment_ind","tags":["account","assignment","clipboard","doc","document","face","ind","people","person","profile","user"]},{"name":"volunteer_activism","tags":["activism","donation","fingers","gesture","giving","hand","hands","heart","love","sharing","volunteer"]},{"name":"navigate_before","tags":["arrow","arrows","before","direction","left","navigate"]},{"name":"published_with_changes","tags":["approve","arrow","arrows","changes","check","complete","done","inprogress","load","loading","mark","ok","published","refresh","renew","replace","rotate","select","tick","validate","verified","with","yes"]},{"name":"add_a_photo","tags":["+","a photo","add","camera","lens","new","photography","picture","plus","symbol"]},{"name":"auto_awesome","tags":["adjust","ai","artificial","automatic","automation","custom","edit","editing","enhance","genai","intelligence","magic","smart","spark","sparkle","star","stars"]},{"name":"card_giftcard","tags":["account","balance","bill","card","cart","cash","certificate","coin","commerce","credit","currency","dollars","gift","giftcard","money","online","pay","payment","present","shopping"]},{"name":"fullscreen","tags":["adjust","app","application","components","full","fullscreen","interface","screen","site","size","ui","ux","view","web","website"]},{"name":"sell","tags":["bill","card","cart","cash","coin","commerce","credit","currency","dollars","money","online","pay","payment","price","sell","shopping","tag"]},{"name":"checklist","tags":["align","alignment","approve","check","checklist","complete","doc","done","edit","editing","editor","format","list","mark","notes","ok","select","sheet","spreadsheet","text","tick","type","validate","verified","writing","yes"]},{"name":"view_in_ar","tags":["3d","ar","augmented","cube","daydream","headset","in","reality","square","view","vr"]},{"name":"undo","tags":["arrow","backward","mail","previous","redo","repeat","rotate","undo"]},{"name":"arrow_drop_up","tags":["app","application","arrow","components","direction","drop","interface","navigation","screen","site","ui","up","ux","web","website"]},{"name":"feedback","tags":["!","alert","announcement","attention","bubble","caution","chat","comment","communicate","danger","error","exclamation","feedback","important","mark","message","notification","speech","symbol","warning"]},{"name":"health_and_safety","tags":["+","add","and","certified","cross","health","home","nest","plus","privacy","private","protect","protection","safety","security","shield","symbol","verified"]},{"name":"work_outline","tags":["bag","baggage","briefcase","business","case","job","suitcase","work"]},{"name":"unfold_more","tags":["arrow","arrows","chevron","collapse","direction","down","expand","expandable","list","more","navigation","unfold"]},{"name":"travel_explore","tags":["earth","explore","find","glass","global","globe","look","magnify","magnifying","map","network","planet","search","see","social","space","travel","web","world"]},{"name":"palette","tags":["art","color","colors","filters","paint","palette"]},{"name":"keyboard_arrow_right","tags":["arrow","arrows","keyboard","right"]},{"name":"double_arrow","tags":["arrow","arrows","direction","double","multiple","navigation","right"]},{"name":"computer","tags":["Android","OS","chrome","computer","desktop","device","hardware","iOS","mac","monitor","web","window"]},{"name":"timeline","tags":["data","history","line","movement","point","points","timeline","tracking","trending","zigzag"]},{"name":"thumb_up_alt","tags":["agreed","approved","confirm","correct","favorite","feedback","good","happy","like","okay","positive","satisfaction","social","thumb","up","vote","yes"]},{"name":"signal_cellular_alt","tags":["alt","analytics","bar","cell","cellular","chart","data","diagram","graph","infographic","internet","measure","metrics","mobile","network","phone","signal","statistics","tracking","wifi","wireless"]},{"name":"replay","tags":["arrow","arrows","control","controls","music","refresh","renew","repeat","replay","video"]},{"name":"swap_horiz","tags":["arrow","arrows","back","forward","horizontal","swap"]},{"name":"volume_off","tags":["audio","control","disabled","enabled","low","music","off","on","slash","sound","speaker","tv","volume"]},{"name":"forum","tags":["bubble","chat","comment","communicate","community","conversation","feedback","forum","hub","message","speech"]},{"name":"skip_next","tags":["arrow","control","controls","music","next","play","previous","skip","video"]},{"name":"water_drop","tags":["drink","drop","droplet","eco","liquid","nature","ocean","rain","social","water"]},{"name":"assignment_turned_in","tags":["approve","assignment","check","clipboard","complete","doc","document","done","in","mark","ok","select","tick","turn","validate","verified","yes"]},{"name":"library_books","tags":["add","album","audio","book","books","collection","library","read","reading"]},{"name":"maps_home_work","tags":["building","home","house","maps","office","work"]},{"name":"dns","tags":["address","bars","dns","domain","information","ip","list","lookup","name","server","system"]},{"name":"sync_alt","tags":["alt","arrow","arrows","horizontal","internet","sync","technology","up","update","wifi"]},{"name":"how_to_reg","tags":["approve","ballot","check","complete","done","election","how","mark","ok","poll","register","registration","select","tick","to reg","validate","verified","vote","yes"]},{"name":"notifications_none","tags":["alarm","alert","bell","none","notifications","notify","reminder","sound"]},{"name":"stars","tags":["achievement","bookmark","circle","favorite","highlight","important","marked","ranking","rate","rating rank","reward","save","saved","shape","special","star"]},{"name":"flight_takeoff","tags":["airport","departed","departing","flight","fly","landing","plane","takeoff","transportation","travel"]},{"name":"label","tags":["favorite","indent","label","library","mail","remember","save","stamp","sticker","tag"]},{"name":"devices","tags":["Android","OS","computer","desktop","device","hardware","iOS","laptop","mobile","monitor","phone","tablet","watch","wearable","web"]},{"name":"chat_bubble","tags":["bubble","chat","comment","communicate","feedback","message","speech"]},{"name":"emoji_emotions","tags":["+","add","emoji","emotions","expressions","face","feelings","glad","happiness","happy","icon","icons","insert","like","mood","new","person","pleased","plus","smile","smiling","social","survey","symbol"]},{"name":"remove_red_eye","tags":["eye","iris","look","looking","preview","red","remove","see","sight","vision"]},{"name":"content_paste","tags":["clipboard","content","copy","cut","doc","document","file","multiple","past"]},{"name":"folder_open","tags":["data","doc","document","drive","file","folder","folders","open","sheet","slide","storage"]},{"name":"text_snippet","tags":["data","doc","document","file","note","notes","snippet","storage","text","writing"]},{"name":"tips_and_updates","tags":["ai","alert","and","announcement","artificial","automatic","automation","custom","electricity","genai","idea","info","information","intelligence","light","lightbulb","magic","smart","spark","sparkle","star","tips","updates"]},{"name":"my_location","tags":["destination","direction","location","maps","navigation","pin","place","point","stop"]},{"name":"textsms","tags":["bubble","chat","comment","communicate","dots","feedback","message","speech","textsms"]},{"name":"cloud","tags":["cloud","connection","internet","network","sky","upload"]},{"name":"sports_esports","tags":["controller","entertainment","esports","game","gamepad","gaming","hobby","online","social","sports","video"]},{"name":"security","tags":["certified","privacy","private","protect","protection","security","shield","verified"]},{"name":"request_quote","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","money","online","pay","payment","price","quote","request","shopping","symbol"]},{"name":"toggle_off","tags":["active","app","application","components","configuration","control","design","disable","inable","inactive","interface","off","on","selection","settings","site","slider","switch","toggle","ui","ux","web","website"]},{"name":"book","tags":["book","bookmark","favorite","label","library","read","reading","remember","ribbon","save","tag"]},{"name":"contact_page","tags":["account","avatar","contact","data","doc","document","drive","face","file","folder","folders","human","page","people","person","profile","sheet","slide","storage","user","writing"]},{"name":"speed","tags":["arrow","control","controls","fast","gauge","meter","motion","music","slow","speed","speedometer","velocity","video"]},{"name":"bug_report","tags":["animal","bug","fix","insect","issue","problem","report","testing","virus","warning"]},{"name":"space_dashboard","tags":["cards","dashboard","format","grid","layout","rectangle","shapes","space","squares","web","website"]},{"name":"fiber_manual_record","tags":["circle","dot","fiber","manual","play","record","watch"]},{"name":"report","tags":["!","alert","attention","caution","danger","error","exclamation","important","mark","notification","octagon","report","symbol","warning"]},{"name":"alarm","tags":["alarm","alert","bell","clock","countdown","date","notification","schedule","time"]},{"name":"cached","tags":["around","arrows","cache","cached","inprogress","load","loading refresh","renew","rotate"]},{"name":"translate","tags":["language","speaking","speech","translate","translator","words"]},{"name":"pan_tool","tags":["fingers","gesture","hand","hands","human","move","pan","scan","stop","tool"]},{"name":"gavel","tags":["agreement","contract","court","document","gavel","government","judge","law","mallet","official","police","rule","rules","terms"]},{"name":"settings_suggest","tags":["ai","artificial","automatic","automation","change","custom","details","gear","genai","intelligence","magic","options","recommendation","service","settings","smart","spark","sparkle","star","suggest","suggestion","system"]},{"name":"file_copy","tags":["content","copy","cut","doc","document","duplicate","file","multiple","past"]},{"name":"edit_calendar","tags":["calendar","compose","create","date","day","draft","edit","editing","event","month","pen","pencil","schedule","write","writing"]},{"name":"contact_mail","tags":["account","address","avatar","communicate","contact","email","face","human","info","information","mail","message","people","person","profile","user"]},{"name":"quiz","tags":["?","assistance","faq","help","info","information","punctuation","question mark","quiz","support","symbol","test"]},{"name":"supervised_user_circle","tags":["account","avatar","circle","control","face","human","parental","parents","people","person","profile","supervised","supervisor","user"]},{"name":"cloud_download","tags":["app","application","arrow","backup","cloud","connection","down","download","drive","files","folders","internet","network","sky","storage","upload"]},{"name":"stop","tags":["control","controls","music","pause","play","square","stop","video"]},{"name":"person_search","tags":["account","avatar","face","find","glass","human","look","magnify","magnifying","people","person","profile","search","user"]},{"name":"location_city","tags":["apartments","architecture","buildings","business","city","estate","home","landscape","location","place","real","residence","residential","shelter","town","urban"]},{"name":"sentiment_very_satisfied","tags":["emotions","expressions","face","feelings","glad","happiness","happy","like","mood","person","pleased","satisfied","sentiment","smile","smiling","survey","very"]},{"name":"ios_share","tags":["arrow","export","ios","send","share","up"]},{"name":"minimize","tags":["app","application","components","design","interface","line","minimize","screen","shape","site","ui","ux","web","website"]},{"name":"qr_code","tags":["barcode","camera","code","media","product","qr","quick","response","smartphone","url","urls"]},{"name":"sentiment_satisfied_alt","tags":["account","alt","emoji","face","happy","human","people","person","profile","satisfied","sentiment","smile","user"]},{"name":"local_mall","tags":["bag","bill","building","business","buy","card","cart","cash","coin","commerce","credit","currency","dollars","handbag","local","mall","money","online","pay","payment","shop","shopping","store","storefront"]},{"name":"qr_code_2","tags":["barcode","camera","code","media","product","qr","quick","response","smartphone","url","urls"]},{"name":"flight","tags":["air","airplane","airport","flight","plane","transportation","travel","trip"]},{"name":"desktop_windows","tags":["Android","OS","chrome","desktop","device","display","hardware","iOS","mac","monitor","screen","television","tv","web","window","windows"]},{"name":"music_note","tags":["audio","audiotrack","key","music","note","sound","track"]},{"name":"sentiment_satisfied","tags":["emotions","expressions","face","feelings","glad","happiness","happy","like","mood","person","pleased","satisfied","sentiment","smile","smiling","survey"]},{"name":"android","tags":["android","character","logo","mascot","toy"]},{"name":"accessibility","tags":["accessibility","accessible","body","handicap","help","human","people","person"]},{"name":"backspace","tags":["arrow","back","backspace","cancel","clear","correct","delete","erase","remove"]},{"name":"precision_manufacturing","tags":["arm","automatic","chain","conveyor","crane","factory","industry","machinery","manufacturing","mechanical","precision","production","repairing","robot","supply","warehouse"]},{"name":"drag_handle","tags":["app","application ui","components","design","drag","handle","interface","layout","menu","move","screen","site","ui","ux","web","website","window"]},{"name":"smart_display","tags":["airplay","cast","chrome","connect","device","display","play","screen","screencast","smart","stream","television","tv","video","wireless"]},{"name":"near_me","tags":["destination","direction","location","maps","me","navigation","near","pin","place","point","stop"]},{"name":"west","tags":["arrow","directional","left","maps","navigation","west"]},{"name":"get_app","tags":["app","arrow","arrows","down","download","downloads","export","get","install","play","upload"]},{"name":"person_add_alt","tags":["+","account","add","face","human","people","person","plus","profile","user"]},{"name":"fitness_center","tags":["athlete","center","dumbbell","exercise","fitness","gym","hobby","places","sport","weights","workout"]},{"name":"shield","tags":["certified","privacy","private","protect","protection","security","shield","verified"]},{"name":"message","tags":["bubble","chat","comment","communicate","feedback","message","speech"]},{"name":"rocket_launch","tags":["launch","rocket","space","spaceship","takeoff"]},{"name":"record_voice_over","tags":["account","face","human","over","people","person","profile","record","recording","speak","speaking","speech","transcript","user","voice"]},{"name":"add_task","tags":["+","add","approve","check","circle","completed","increase","mark","ok","plus","select","task","tick","yes"]},{"name":"drive_file_rename_outline","tags":["compose","create","draft","drive","edit","editing","file","input","marker","pen","pencil","rename","write","writing"]},{"name":"insert_drive_file","tags":["doc","drive","file","format","insert","sheet","slide"]},{"name":"question_mark","tags":["?","assistance","help","info","information","punctuation","question mark","support","symbol"]},{"name":"trending_flat","tags":["arrow","change","data","flat","metric","movement","rate","right","track","tracking","trending"]},{"name":"handyman","tags":["build","construction","fix","hammer","handyman","repair","screw","screwdriver","tools"]},{"name":"emoji_objects","tags":["bulb","creative","emoji","idea","light","objects","solution","thinking"]},{"name":"military_tech","tags":["army","award","badge","honor","medal","merit","military","order","privilege","prize","rank","reward","ribbon","soldier","star","status","tech","trophy","win","winner"]},{"name":"hourglass_empty","tags":["countdown","empty","hourglass","loading","minutes","time","wait","waiting"]},{"name":"help_center","tags":["?","assistance","center","help","info","information","punctuation","question mark","recent","restore","support","symbol"]},{"name":"science","tags":["beaker","chemical","chemistry","experiment","flask","glass","laboratory","research","science","tube"]},{"name":"storage","tags":["computer","data","drive","memory","storage"]},{"name":"movie","tags":["cinema","film","media","movie","slate","video"]},{"name":"accessibility_new","tags":["accessibility","accessible","body","handicap","help","human","new","people","person"]},{"name":"workspace_premium","tags":["certification","degree","ecommerce","guarantee","medal","permit","premium","ribbon","verification","workspace"]},{"name":"directions_run","tags":["body","directions","human","jogging","maps","people","person","route","run","running","walk"]},{"name":"rule","tags":["approve","check","complete","done","incomplete","line","mark","missing","no","ok","rule","select","tick","validate","verified","wrong","x","yes"]},{"name":"thumb_down","tags":["ate","dislike","down","favorite","fingers","gesture","hand","hands","like","rank","ranking","rating","thumb"]},{"name":"event_note","tags":["calendar","date","event","note","schedule","text","time","writing"]},{"name":"contacts","tags":["account","avatar","call","cell","contacts","face","human","info","information","mobile","people","person","phone","profile","user"]},{"name":"comment","tags":["bubble","chat","comment","communicate","feedback","message","outline","speech"]},{"name":"restaurant_menu","tags":["book","dining","eat","food","fork","knife","local","meal","menu","restaurant","spoon"]},{"name":"add_photo_alternate","tags":["+","add","alternate","image","landscape","mountain","mountains","new","photo","photography","picture","plus","symbol"]},{"name":"confirmation_number","tags":["admission","confirmation","entertainment","event","number","ticket"]},{"name":"sticky_note_2","tags":["2","bookmark","mark","message","note","paper","sticky","text","writing"]},{"name":"format_quote","tags":["doc","edit","editing","editor","format","quotation","quote","sheet","spreadsheet","text","type","writing"]},{"name":"history_edu","tags":["document","edu","education","feather","history","letter","paper","pen","quill","school","story","tools","write","writing"]},{"name":"business_center","tags":["bag","baggage","briefcase","business","case","center","places","purse","suitcase","work"]},{"name":"upload","tags":["arrow","arrows","download","drive","up","upload"]},{"name":"skip_previous","tags":["arrow","control","controls","music","next","play","previous","skip","video"]},{"name":"archive","tags":["archive","inbox","mail","store"]},{"name":"wb_sunny","tags":["balance","bright","light","lighting","sun","sunny","wb","white"]},{"name":"cake","tags":["add","baked","birthday","cake","candles","celebration","dessert","food","frosting","new","party","pastries","pastry","plus","social","sweet","symbol"]},{"name":"attachment","tags":["attach","attachment","clip","compose","file","image","link"]},{"name":"source","tags":["code","composer","content","creation","data","doc","document","file","folder","mode","source","storage","view"]},{"name":"settings_applications","tags":["application","change","details","gear","info","information","options","personal","service","settings"]},{"name":"dashboard_customize","tags":["cards","customize","dashboard","format","layout","rectangle","shapes","square","web","website"]},{"name":"find_in_page","tags":["data","doc","document","drive","file","find","folder","folders","glass","in","look","magnify","magnifying","page","paper","search","see","sheet","slide","writing"]},{"name":"support","tags":["assist","buoy","help","life","lifebuoy","rescue","safe","safety","support"]},{"name":"ads_click","tags":["ads","browser","click","clicks","cursor","internet","target","traffic","web"]},{"name":"new_releases","tags":["approve","award","check","checkmark","complete","done","new","notification","ok","release","releases","select","star","symbol","tick","verification","verified","warning","yes"]},{"name":"flutter_dash","tags":["bird","dash","flutter","mascot"]},{"name":"playlist_add","tags":["+","add","collection","list","music","new","playlist","plus","symbol"]},{"name":"save_alt","tags":["alt","arrow","disk","document","down","file","floppy","multimedia","save"]},{"name":"close_fullscreen","tags":["action","arrow","arrows","close","collapse","direction","full","fullscreen","minimize","screen"]},{"name":"credit_score","tags":["approve","bill","card","cash","check","coin","commerce","complete","cost","credit","currency","dollars","done","finance","loan","mark","money","ok","online","pay","payment","score","select","symbol","tick","validate","verified","yes"]},{"name":"layers","tags":["arrange","disabled","enabled","interaction","layers","maps","off","on","overlay","pages","slash"]},{"name":"redeem","tags":["bill","card","cart","cash","certificate","coin","commerce","credit","currency","dollars","gift","giftcard","money","online","pay","payment","present","redeem","shopping"]},{"name":"spa","tags":["aromatherapy","flower","healthcare","leaf","massage","meditation","nature","petals","places","relax","spa","wellbeing","wellness"]},{"name":"announcement","tags":["!","alert","announcement","attention","bubble","caution","chat","comment","communicate","danger","error","exclamation","feedback","important","mark","message","notification","speech","symbol","warning"]},{"name":"keyboard_backspace","tags":["arrow","back","backspace","keyboard","left"]},{"name":"loyalty","tags":["benefits","card","credit","heart","loyalty","membership","miles","points","program","subscription","tag","travel","trip"]},{"name":"swap_vert","tags":["arrow","arrows","direction","down","navigation","swap","up","vert","vertical"]},{"name":"sentiment_dissatisfied","tags":["angry","disappointed","dislike","dissatisfied","emotions","expressions","face","feelings","frown","mood","person","sad","sentiment","survey","unhappy","unsatisfied","upset"]},{"name":"medical_services","tags":["aid","bag","briefcase","emergency","first","kit","medical","medicine","services"]},{"name":"view_headline","tags":["design","format","grid","headline","layout","paragraph","text","view","website"]},{"name":"arrow_circle_right","tags":["arrow","circle","direction","navigation","right"]},{"name":"format_list_numbered","tags":["align","alignment","digit","doc","edit","editing","editor","format","list","notes","number","numbered","sheet","spreadsheet","symbol","text","type","writing"]},{"name":"phone_android","tags":["OS","android","cell","device","hardware","iOS","mobile","phone","tablet"]},{"name":"sms","tags":["3","bubble","chat","communication","conversation","dots","message","more","service","sms","speech","three"]},{"name":"restore","tags":["arrow","back","backwards","clock","date","history","refresh","renew","restore","reverse","rotate","schedule","time","turn"]},{"name":"policy","tags":["certified","find","glass","legal","look","magnify","magnifying","policy","privacy","private","protect","protection","search","security","see","shield","verified"]},{"name":"dangerous","tags":["broken","danger","dangerous","fix","no","sign","stop","update","warning","wrong","x"]},{"name":"battery_full","tags":["battery","cell","charge","full","mobile","power"]},{"name":"euro_symbol","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","euro","finance","money","online","pay","payment","symbol"]},{"name":"query_stats","tags":["analytics","chart","data","diagram","find","glass","graph","infographic","line","look","magnify","magnifying","measure","metrics","query","search","see","statistics","stats","tracking"]},{"name":"group_work","tags":["alliance","collaboration","group","partnership","team","teamwork","together","work"]},{"name":"expand_circle_down","tags":["arrow","arrows","chevron","circle","collapse","direction","down","expand","expandable","list","more"]},{"name":"sensors","tags":["connection","network","scan","sensors","signal","wireless"]},{"name":"keyboard_arrow_up","tags":["arrow","arrows","keyboard","up"]},{"name":"brush","tags":["art","brush","design","draw","edit","editing","paint","painting","tool"]},{"name":"meeting_room","tags":["building","door","doorway","entrance","home","house","interior","meeting","office","open","places","room"]},{"name":"key","tags":["key","lock","password","unlock"]},{"name":"house","tags":["architecture","building","estate","family","home","homepage","house","place","places","real","residence","residential","shelter"]},{"name":"lunch_dining","tags":["breakfast","dining","dinner","drink","fastfood","food","hamburger","lunch","meal"]},{"name":"table_chart","tags":["analytics","bar","bars","chart","data","diagram","graph","infographic grid","measure","metrics","statistics","table","tracking"]},{"name":"border_color","tags":["all","border","doc","edit","editing","editor","pen","pencil","sheet","spreadsheet","stroke","text","type","writing"]},{"name":"compare_arrows","tags":["arrow","arrows","collide","compare","direction","left","pressure","push","right","together"]},{"name":"south","tags":["arrow","directional","down","maps","navigation","south"]},{"name":"directions_walk","tags":["body","direction","directions","human","jogging","maps","people","person","route","run","walk"]},{"name":"arrow_left","tags":["app","application","arrow","components","direction","interface","left","navigation","screen","site","ui","ux","web","website"]},{"name":"tag","tags":["hash","hashtag","key","media","number","pound","social","tag","trend"]},{"name":"change_circle","tags":["around","arrows","change","circle","direction","navigation","rotate"]},{"name":"subject","tags":["alignment","doc","document","email","full","justify","list","note","subject","text","writing"]},{"name":"sentiment_very_dissatisfied","tags":["angry","disappointed","dislike","dissatisfied","emotions","expressions","face","feelings","mood","person","sad","sentiment","sorrow","survey","unhappy","unsatisfied","upset","very"]},{"name":"local_hospital","tags":["911","aid","cross","emergency","first","hospital","local","medicine"]},{"name":"table_view","tags":["format","grid","group","layout","multiple","table","view"]},{"name":"disabled_by_default","tags":["box","by","cancel","close","default","disabled","exit","no","quit","remove","square","stop","x"]},{"name":"notification_important","tags":["!","active","alarm","alert","attention","bell","caution","chime","danger","error","exclamation","important","mark","notification","notifications","notify","reminder","ring","sound","symbol","warning"]},{"name":"celebration","tags":["activity","birthday","celebration","event","fun","party"]},{"name":"laptop","tags":["Android","OS","chrome","computer","desktop","device","hardware","iOS","laptop","mac","monitor","web","windows"]},{"name":"loop","tags":["around","arrow","arrows","direction","inprogress","load","loading refresh","loop","music","navigation","renew","rotate","turn"]},{"name":"nightlight_round","tags":["dark","half","light","mode","moon","night","nightlight","round"]},{"name":"privacy_tip","tags":["alert","announcement","assistance","certified","details","help","i","info","information","privacy","private","protect","protection","security","service","shield","support","tip","verified"]},{"name":"import_contacts","tags":["address","book","contacts","import","info","information","open"]},{"name":"equalizer","tags":["adjustment","analytics","chart","data","equalizer","graph","measure","metrics","music","noise","sound","static","statistics","tracking","volume"]},{"name":"app_registration","tags":["app","apps","edit","pencil","register","registration"]},{"name":"keyboard_double_arrow_right","tags":["arrow","arrows","direction","double","multiple","navigation","right"]},{"name":"handshake","tags":["agreement","hand","hands","partnership","shake"]},{"name":"corporate_fare","tags":["architecture","building","business","corporate","estate","fare","organization","place","real","residence","residential","shelter"]},{"name":"local_library","tags":["book","community learning","library","local","read"]},{"name":"https","tags":["https","lock","locked","password","privacy","private","protection","safety","secure","security"]},{"name":"euro","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","euro","euros","finance","money","online","pay","payment","price","shopping","symbol"]},{"name":"coronavirus","tags":["19","bacteria","coronavirus","covid","disease","germs","illness","sick","social"]},{"name":"price_check","tags":["approve","bill","card","cash","check","coin","commerce","complete","cost","credit","currency","dollars","done","finance","mark","money","ok","online","pay","payment","price","select","shopping","symbol","tick","validate","verified","yes"]},{"name":"live_tv","tags":["Android","OS","antennas hardware","chrome","desktop","device","iOS","live","mac","monitor","movie","play","stream","television","tv","web","window"]},{"name":"park","tags":["attraction","fresh","local","nature","outside","park","plant","tree"]},{"name":"toc","tags":["content","format","lines","list","order","reorder","stacked","table","title","titles","toc"]},{"name":"track_changes","tags":["bullseye","changes","circle","evolve","lines","movement","rotate","shift","target","track"]},{"name":"arrow_circle_up","tags":["arrow","circle","direction","navigation","up"]},{"name":"emoji_people","tags":["arm","body","emoji","greeting","human","people","person","social","waving"]},{"name":"flash_on","tags":["bolt","disabled","electric","enabled","fast","flash","lightning","off","on","slash","thunderbolt"]},{"name":"copyright","tags":["alphabet","c","character","copyright","emblem","font","legal","letter","owner","symbol","text"]},{"name":"bookmarks","tags":["bookmark","bookmarks","favorite","label","layers","library","multiple","read","reading","remember","ribbon","save","stack","tag"]},{"name":"ac_unit","tags":["ac","air","cold","conditioner","flake","snow","temperature","unit","weather","winter"]},{"name":"contact_phone","tags":["account","avatar","call","communicate","contact","face","human","info","information","message","mobile","people","person","phone","profile","user"]},{"name":"keyboard_arrow_left","tags":["arrow","arrows","keyboard","left"]},{"name":"medication","tags":["doctor","drug","emergency","hospital","medication","medicine","pharmacy","pills","prescription"]},{"name":"grading","tags":["'favorite'_new'. ' Remove this icon & keep 'star'.","'star_boarder'","'star_border_purple500'","'star_outline'","'star_purple500'","'star_rate'","Same as 'star'"]},{"name":"keyboard_return","tags":["arrow","back","keyboard","left","return"]},{"name":"api","tags":["api","developer","development","enterprise","software"]},{"name":"smart_toy","tags":["bot","droid","games","robot","smart","toy"]},{"name":"input","tags":["arrow","box","download","input","login","move","right"]},{"name":"self_improvement","tags":["body","calm","care","chi","human","improvement","meditate","meditation","people","person","relax","self","sitting","wellbeing","yoga","zen"]},{"name":"live_help","tags":["?","assistance","bubble","chat","comment","communicate","help","info","information","live","message","punctuation","question mark","recent","restore","speech","support","symbol"]},{"name":"query_builder","tags":["builder","clock","date","query","schedule","time"]},{"name":"perm_media","tags":["collection","data","doc","document","file","folder","folders","image","landscape","media","mountain","mountains","perm","photo","photography","picture","storage"]},{"name":"download_for_offline","tags":["arrow","circle","down","download","for offline","install","upload"]},{"name":"view_module","tags":["design","format","grid","layout","module","square","squares","stacked","view","website"]},{"name":"pin","tags":["1","2","3","digit","key","login","logout","number","password","pattern","pin","security","star","symbol","unlock"]},{"name":"fast_forward","tags":["control","fast","forward","media","music","play","speed","time","tv","video"]},{"name":"forward_to_inbox","tags":["arrow","arrows","directions","email","envelop","forward","inbox","letter","mail","message","navigation","outgoing","right","send","to"]},{"name":"person_remove","tags":["account","avatar","delete","face","human","minus","people","person","profile","remove","unfriend","user"]},{"name":"local_atm","tags":["atm","bill","card","cart","cash","coin","commerce","credit","currency","dollars","local","money","online","pay","payment","shopping","symbol"]},{"name":"star_half","tags":["achievement","bookmark","favorite","half","highlight","important","marked","ranking","rate","rating rank","reward","save","saved","shape","special","star","toggle"]},{"name":"build_circle","tags":["adjust","build","circle","fix","repair","tool","wrench"]},{"name":"redo","tags":["arrow","backward","forward","next","redo","repeat","rotate","undo"]},{"name":"web","tags":["browser","internet","page","screen","site","web","website","www"]},{"name":"north_east","tags":["arrow","east","maps","navigation","noth","right","up"]},{"name":"north","tags":["arrow","directional","maps","navigation","north","up"]},{"name":"cottage","tags":["architecture","beach","cottage","estate","home","house","lake","lodge","maps","place","real","residence","residential","stay","traveling"]},{"name":"local_activity","tags":["activity","event","event ticket","local","star","things","ticket"]},{"name":"currency_exchange","tags":["360","around","arrow","arrows","cash","coin","commerce","currency","direction","dollars","exchange","inprogress","money","pay","renew","rotate","sync","turn","universal"]},{"name":"video_library","tags":["arrow","collection","library","play","video"]},{"name":"hourglass_bottom","tags":["bottom","countdown","half","hourglass","loading","minute","minutes","time","wait","waiting"]},{"name":"headphones","tags":["accessory","audio","device","ear","earphone","headphones","headset","listen","music","sound"]},{"name":"zoom_out","tags":["find","glass","look","magnify","magnifying","minus","negative","out","scale","search","see","size","small","smaller","zoom"]},{"name":"poll","tags":["analytics","bar","bars","chart","data","diagram","graph","infographic","measure","metrics","poll","statistics","survey","tracking","vote"]},{"name":"perm_contact_calendar","tags":["account","calendar","contact","date","face","human","information","people","perm","person","profile","schedule","time","user"]},{"name":"forward","tags":["arrow","forward","mail","message","playback","right","sent"]},{"name":"person_pin","tags":["account","avatar","destination","direction","face","human","location","maps","people","person","pin","place","profile","stop","user"]},{"name":"home_work","tags":["architecture","building","estate","home","place","real","residence","residential","shelter","work"]},{"name":"playlist_add_check","tags":["add","approve","check","collection","complete","done","list","mark","music","ok","playlist","select","tick","validate","verified","yes"]},{"name":"local_cafe","tags":["bottle","cafe","coffee","cup","drink","food","restaurant","tea"]},{"name":"ondemand_video","tags":["Android","OS","chrome","demand","desktop","device","hardware","iOS","mac","monitor","ondemand","play","television","tv","video","web","window"]},{"name":"design_services","tags":["compose","create","design","draft","edit","editing","input","pen","pencil","ruler","service","write","writing"]},{"name":"looks_one","tags":["1","digit","looks","numbers","square","symbol"]},{"name":"backup","tags":["arrow","backup","cloud","data","drive","files folders","storage","up","upload"]},{"name":"newspaper","tags":["article","data","doc","document","drive","file","folder","folders","magazine","media","news","newspaper","notes","page","paper","sheet","slide","text","writing"]},{"name":"memory","tags":["card","chip","digital","memory","micro","processor","sd","storage"]},{"name":"open_with","tags":["arrow","arrows","direction","expand","move","open","pan","with"]},{"name":"content_cut","tags":["content","copy","cut","doc","document","file","past","scissors","trim"]},{"name":"keyboard","tags":["computer","device","hardware","input","keyboard","keypad","letter","office","text","type"]},{"name":"hourglass_top","tags":["countdown","half","hourglass","loading","minute","minutes","time","top","wait","waiting"]},{"name":"settings_phone","tags":["call","cell","contact","device","hardware","mobile","phone","settings","telephone"]},{"name":"rss_feed","tags":["application","blog","connection","data","feed","internet","network","rss","service","signal","website","wifi","wireless"]},{"name":"first_page","tags":["arrow","back","chevron","first","left","page","rewind"]},{"name":"delivery_dining","tags":["delivery","dining","food","meal","restaurant","scooter","takeout","transportation","vehicle","vespa"]},{"name":"rate_review","tags":["comment","feedback","pen","pencil","rate","review","stars","write"]},{"name":"control_point","tags":["+","add","circle","control","plus","point"]},{"name":"gpp_good","tags":["certified","check","good","gpp","ok","pass","security","shield","sim","tick"]},{"name":"circle_notifications","tags":["active","alarm","alert","bell","chime","circle","notifications","notify","reminder","ring","sound"]},{"name":"auto_fix_high","tags":["adjust","ai","artificial","auto","automatic","automation","custom","edit","editing","enhance","erase","fix","genai","high","intelligence","magic","modify","pen","smart","spark","sparkle","star","tool","wand"]},{"name":"book_online","tags":["Android","OS","admission","appointment","book","cell","device","event","hardware","iOS","mobile","online","pass","phone","reservation","tablet","ticket"]},{"name":"notes","tags":["comment","doc","document","note","notes","text","write","writing"]},{"name":"point_of_sale","tags":["checkout","cost","machine","merchant","money","of","pay","payment","point","pos","retail","sale","system","transaction"]},{"name":"perm_phone_msg","tags":["bubble","call","cell","chat","comment","communicate","contact","device","message","msg","perm","phone","recording","speech","telephone","voice"]},{"name":"speaker_notes","tags":["bubble","chat","comment","communicate","format","list","message","notes","speaker","speech","text"]},{"name":"fullscreen_exit","tags":["adjust","app","application","components","exit","full","fullscreen","interface","screen","site","size","ui","ux","view","web","website"]},{"name":"headset_mic","tags":["accessory","audio","chat","device","ear","earphone","headphones","headset","listen","mic","music","sound","talk"]},{"name":"create_new_folder","tags":["+","add","create","data","doc","document","drive","file","folder","new","plus","sheet","slide","storage","symbol"]},{"name":"wysiwyg","tags":["composer","mode","screen","site","software","system","text","view","visibility","web","website","window","wysiwyg"]},{"name":"label_important","tags":["favorite","important","indent","label","library","mail","remember","save","stamp","sticker","tag","wing"]},{"name":"card_membership","tags":["bill","bookmark","card","cash","certificate","coin","commerce","cost","credit","currency","dollars","finance","loyalty","membership","money","online","pay","payment","shopping","subscription"]},{"name":"style","tags":["booklet","cards","filters","options","style","tags"]},{"name":"arrow_circle_down","tags":["arrow","circle","direction","down","navigation"]},{"name":"file_present","tags":["clip","data","doc","document","drive","file","folder","folders","note","paper","present","reminder","sheet","slide","storage","writing"]},{"name":"directions_bus","tags":["automobile","bus","car","cars","directions","maps","public","transportation","vehicle"]},{"name":"whatshot","tags":["arrow","circle","direction","fire","frames","hot","round","whatshot"]},{"name":"sports_soccer","tags":["athlete","athletic","ball","entertainment","exercise","football","game","hobby","soccer","social","sports"]},{"name":"indeterminate_check_box","tags":["app","application","box","button","check","components","control","design","form","indeterminate","interface","screen","select","selected","selection","site","square","toggle","ui","undetermined","ux","web","website"]},{"name":"outlined_flag","tags":["country","flag","goal","mark","nation","outlined","report","start"]},{"name":"price_change","tags":["arrows","bill","card","cash","change","coin","commerce","cost","credit","currency","dollars","down","finance","money","online","pay","payment","price","shopping","symbol","up"]},{"name":"mark_email_read","tags":["approve","check","complete","done","email","envelop","letter","mail","mark","message","note","ok","read","select","send","sent","tick","yes"]},{"name":"library_add","tags":["+","add","collection","layers","library","multiple","music","new","plus","stacked","symbol","video"]},{"name":"pageview","tags":["doc","document","find","glass","magnifying","page","paper","search","view"]},{"name":"tv","tags":["device","display","monitor","screen","screencast","stream","television","tv","video","wireless"]},{"name":"inbox","tags":["archive","email","inbox","incoming","mail","message"]},{"name":"adjust","tags":["adjust","alter","center","circle","circles","dot","fix","image","move","target"]},{"name":"3d_rotation","tags":["3","3d","D","alphabet","arrow","arrows","av","camera","character","digit","font","letter","number","rotation","symbol","text","type","vr"]},{"name":"battery_charging_full","tags":["battery","bolt","cell","charge","charging","full","lightening","mobile","power","thunderbolt"]},{"name":"chair","tags":["chair","comfort","couch","decoration","furniture","home","house","living","lounging","loveseat","room","seat","seating","sofa"]},{"name":"directions_bike","tags":["bicycle","bike","direction","directions","human","maps","person","public","route","transportation"]},{"name":"mic_off","tags":["audio","disabled","enabled","hear","hearing","mic","microphone","noise","off","on","record","recording","slash","sound","voice"]},{"name":"local_police","tags":["911","badge","law","local","officer","police","protect","protection","security","shield"]},{"name":"fastfood","tags":["drink","fastfood","food","hamburger","maps","meal","places"]},{"name":"tungsten","tags":["electricity","indoor","lamp","light","lightbulb","setting","tungsten"]},{"name":"mood","tags":["emoji","emotions","expressions","face","feelings","glad","happiness","happy","like","mood","person","pleased","smile","smiling","social","survey"]},{"name":"pause_circle","tags":["circle","control","controls","media","music","pause","video"]},{"name":"upgrade","tags":["arrow","export","instal","line","replace","up","update","upgrade"]},{"name":"recommend","tags":["approved","circle","confirm","favorite","gesture","hand","like","reaction","recommend","social","support","thumbs","up","well"]},{"name":"directions_car_filled","tags":["automobile","car","cars","direction","directions","filled","maps","public","transportation","vehicle"]},{"name":"fmd_good","tags":["destination","direction","fmd","good","location","maps","pin","place","stop"]},{"name":"integration_instructions","tags":["brackets","clipboard","code","css","develop","developer","doc","document","engineer","engineering clipboard","html","instructions","integration","platform"]},{"name":"format_bold","tags":["B","alphabet","bold","character","doc","edit","editing","editor","font","format","letter","sheet","spreadsheet","styles","symbol","text","type","writing"]},{"name":"people_outline","tags":["accounts","committee","face","family","friends","humans","network","outline","people","persons","profiles","social","team","users"]},{"name":"trending_down","tags":["analytics","arrow","data","diagram","down","graph","infographic","measure","metrics","movement","rate","rating","statistics","tracking","trending"]},{"name":"change_history","tags":["change","history","shape","triangle"]},{"name":"female","tags":["female","gender","girl","lady","social","symbol","woman","women"]},{"name":"link_off","tags":["attached","chain","clip","connection","disabled","enabled","link","linked","links","multimedia","off","on","slash","url"]},{"name":"text_fields","tags":["T","add","alphabet","character","field","fields","font","input","letter","symbol","text","type"]},{"name":"swipe","tags":["arrow","arrows","fingers","gesture","hand","hands","swipe","touch"]},{"name":"reviews","tags":["bubble","chat","comment","communicate","feedback","message","rate","rating","recommendation","reviews","speech"]},{"name":"home_repair_service","tags":["box","equipment","fix","home","kit","mechanic","repair","repairing","service","tool","toolbox","tools","workshop"]},{"name":"subscriptions","tags":["enroll","list","media","order","play","signup","subscribe","subscriptions"]},{"name":"video_call","tags":["+","add","call","camera","chat","conference","film","filming","hardware","image","motion","new","picture","plus","symbol","video","videography"]},{"name":"zoom_out_map","tags":["arrow","arrows","destination","location","maps","move","out","place","stop","zoom"]},{"name":"straighten","tags":["length","measure","measurement","ruler","size","straighten"]},{"name":"arrow_drop_down_circle","tags":["app","application","arrow","circle","components","direction","down","drop","interface","navigation","screen","site","ui","ux","web","website"]},{"name":"bed","tags":["bed","bedroom","double","full","furniture","home","hotel","house","king","night","pillows","queen","rest","room","size","sleep"]},{"name":"drive_eta","tags":["automobile","car","cars","destination","direction","drive","estimate","eta","maps","public","transportation","travel","trip","vehicle"]},{"name":"class","tags":["archive","book","bookmark","class","favorite","label","library","read","reading","remember","ribbon","save","tag"]},{"name":"drafts","tags":["document","draft","drafts","email","file","letter","mail","message","read"]},{"name":"ballot","tags":["ballot","bullet","election","list","point","poll","vote"]},{"name":"volume_mute","tags":["audio","control","music","mute","sound","speaker","tv","volume"]},{"name":"table_rows","tags":["grid","layout","lines","rows","stacked","table"]},{"name":"accessible","tags":["accessibility","accessible","body","handicap","help","human","people","person","wheelchair"]},{"name":"stop_circle","tags":["circle","control","controls","music","pause","play","square","stop","video"]},{"name":"family_restroom","tags":["bathroom","child","children","family","father","kids","mother","parents","restroom","wc"]},{"name":"title","tags":["T","alphabet","character","font","header","letter","subject","symbol","text","title","type"]},{"name":"biotech","tags":["biotech","chemistry","laboratory","microscope","research","science","technology"]},{"name":"insert_emoticon","tags":["account","emoji","emoticon","face","happy","human","insert","people","person","profile","sentiment","smile","user"]},{"name":"g_translate","tags":["emblem","g","google","language","logo","mark","speaking","speech","translate","translator","words"]},{"name":"last_page","tags":["app","application","arrow","chevron","components","end","forward","interface","last","page","right","screen","site","ui","ux","web","website"]},{"name":"publish","tags":["arrow","cloud","file","import","publish","up","upload"]},{"name":"repeat","tags":["arrow","arrows","control","controls","media","music","repeat","video"]},{"name":"checklist_rtl","tags":["align","alignment","approve","check","checklist","complete","doc","done","edit","editing","editor","format","list","mark","notes","ok","rtl","select","sheet","spreadsheet","text","tick","type","validate","verified","writing","yes"]},{"name":"wifi_off","tags":["connection","data","disabled","enabled","internet","network","off","offline","on","scan","service","signal","slash","wifi","wireless"]},{"name":"settings_accessibility","tags":["accessibility","body","details","human","information","people","person","personal","preferences","profile","settings","user"]},{"name":"percent","tags":["math","number","percent","symbol"]},{"name":"insert_photo","tags":["image","insert","landscape","mountain","mountains","photo","photography","picture"]},{"name":"hotel","tags":["body","hotel","human","people","person","sleep","stay","travel","trip"]},{"name":"cleaning_services","tags":["clean","cleaning","dust","services","sweep"]},{"name":"downloading","tags":["arrow","circle","down","download","downloading","downloads","install","pending","progress","upload"]},{"name":"expand","tags":["arrow","arrows","compress","enlarge","expand","grow","move","push","together"]},{"name":"local_phone","tags":["booth","call","communication","phone","telecommunication"]},{"name":"offline_bolt","tags":["bolt","circle","electric","fast","lightning","offline","thunderbolt"]},{"name":"auto_graph","tags":["analytics","auto","chart","data","diagram","graph","infographic","line","measure","metrics","stars","statistics","tracking"]},{"name":"local_grocery_store","tags":["grocery","market","shop","store"]},{"name":"photo_library","tags":["album","image","library","mountain","mountains","photo","photography","picture"]},{"name":"miscellaneous_services","tags":[]},{"name":"note_alt","tags":["alt","clipboard","document","file","memo","note","page","paper","writing"]},{"name":"settings_backup_restore","tags":["arrow","back","backup","backwards","refresh","restore","reverse","rotate","settings"]},{"name":"production_quantity_limits","tags":["!","alert","attention","bill","card","cart","cash","caution","coin","commerce","credit","currency","danger","dollars","error","exclamation","important","limits","mark","money","notification","online","pay","payment","production","quantity","shopping","symbol","warning"]},{"name":"person_off","tags":["account","avatar","disabled","enabled","face","human","off","on","people","person","profile","slash","user"]},{"name":"report_gmailerrorred","tags":["!","alert","attention","caution","danger","error","exclamation","gmail","gmailerrorred","important","mark","notification","octagon","report","symbol","warning"]},{"name":"camera","tags":["aperture","camera","lens","photo","photography","picture","shutter"]},{"name":"recycling","tags":["bio","eco","green","loop","recyclable","recycle","recycling","rotate","sustainability","sustainable","trash"]},{"name":"male","tags":["boy","gender","male","man","social","symbol"]},{"name":"not_interested","tags":["cancel","close","dislike","exit","interested","no","not","off","quit","remove","stop","x"]},{"name":"event_busy","tags":["busy","calendar","cancel","close","date","event","exit","no","remove","schedule","stop","time","unavailable","x"]},{"name":"arrow_circle_left","tags":["arrow","circle","direction","left","navigation"]},{"name":"shuffle","tags":["arrow","arrows","control","controls","music","random","shuffle","video"]},{"name":"aspect_ratio","tags":["aspect","expand","image","ratio","resize","scale","size","square"]},{"name":"other_houses","tags":["architecture","cottage","estate","home","house","houses","maps","other","place","real","residence","residential","stay","traveling"]},{"name":"model_training","tags":["arrow","bulb","idea","inprogress","light","load","loading","model","refresh","renew","restore","reverse","rotate","training"]},{"name":"unfold_less","tags":["arrow","arrows","chevron","collapse","direction","expand","expandable","inward","less","list","navigation","unfold","up"]},{"name":"insert_chart_outlined","tags":["analytics","bar","bars","chart","data","diagram","graph","infographic","insert","measure","metrics","outlined","statistics","tracking"]},{"name":"donut_large","tags":["analytics","chart","data","diagram","donut","graph","infographic","inprogress","large","measure","metrics","pie","statistics","tracking"]},{"name":"view_column","tags":["column","design","format","grid","layout","vertical","view","website"]},{"name":"segment","tags":["alignment","fonts","format","lines","list","paragraph","part","piece","rule","rules","segment","style","text"]},{"name":"checkroom","tags":["checkroom","closet","clothes","coat check","hanger"]},{"name":"mode","tags":["compose","create","draft","draw","edit","mode","pen","pencil","write"]},{"name":"portrait","tags":["account","face","human","people","person","photo","picture","portrait","profile","user"]},{"name":"camera_alt","tags":["alt","camera","image","photo","photography","picture"]},{"name":"keyboard_double_arrow_left","tags":["arrow","arrows","direction","double","left","multiple","navigation"]},{"name":"delete_sweep","tags":["bin","can","delete","garbage","remove","sweep","trash"]},{"name":"hub","tags":["center","connection","core","focal point","hub","network","nucleus","topology"]},{"name":"audiotrack","tags":["audio","audiotrack","key","music","note","sound","track"]},{"name":"calendar_view_month","tags":["calendar","date","day","event","format","grid","layout","month","schedule","today","view"]},{"name":"draw","tags":["compose","create","design","draft","draw","edit","editing","input","pen","pencil","write","writing"]},{"name":"navigation","tags":["destination","direction","location","maps","navigation","pin","place","point","stop"]},{"name":"folder_shared","tags":["account","collaboration","data","doc","document","drive","face","file","folder","human","people","person","profile","share","shared","sheet","slide","storage","team","user"]},{"name":"read_more","tags":["arrow","more","read","text"]},{"name":"stacked_bar_chart","tags":["analytics","bar","chart-chart","data","diagram","graph","infographic","measure","metrics","stacked","statistics","tracking"]},{"name":"mode_comment","tags":["bubble","chat","comment","communicate","feedback","message","mode comment","speech"]},{"name":"schedule_send","tags":["calendar","clock","date","email","letter","mail","remember","schedule","send","share","time"]},{"name":"bluetooth","tags":["bluetooth","cast","connect","connection","device","paring","streaming","symbol","wireless"]},{"name":"graphic_eq","tags":["audio","eq","equalizer","graphic","music","recording","sound","voice"]},{"name":"markunread","tags":["email","envelop","letter","mail","markunread","message","send","unread"]},{"name":"alarm_on","tags":["alarm","alert","bell","clock","disabled","duration","enabled","notification","off","on","slash","time","timer","watch"]},{"name":"local_gas_station","tags":["auto","car","gas","local","oil","station","vehicle"]},{"name":"person_add_alt_1","tags":[]},{"name":"maximize","tags":["app","application","components","design","interface","line","maximize","screen","shape","site","ui","ux","web","website"]},{"name":"bookmark_add","tags":["+","add","bookmark","favorite","plus","remember","ribbon","save","symbol"]},{"name":"dvr","tags":["Android","OS","audio","chrome","computer","desktop","device","display","dvr","electronic","hardware","iOS","list","mac","monitor","record","recorder","screen","tv","video","web","window"]},{"name":"do_not_disturb_on","tags":["cancel","close","denied","deny","disabled","disturb","do","enabled","off","on","remove","silence","slash","stop"]},{"name":"train","tags":["automobile","car","cars","direction","maps","public","rail","subway","train","transportation","vehicle"]},{"name":"person_pin_circle","tags":["account","circle","destination","direction","face","human","location","maps","people","person","pin","place","profile","stop","user"]},{"name":"square_foot","tags":["construction","feet","foot","inches","length","measurement","ruler","school","set","square","tools"]},{"name":"more_time","tags":["+","add","clock","date","more","new","plus","schedule","symbol","time"]},{"name":"document_scanner","tags":["article","data","doc","document","drive","file","folder","folders","notes","page","paper","scan","scanner","sheet","slide","text","writing"]},{"name":"thumbs_up_down","tags":["dislike","down","favorite","fingers","gesture","hands","like","rate","rating","thumbs","up"]},{"name":"settings_ethernet","tags":["arrows","computer","connect","connection","connectivity","dots","ethernet","internet","network","settings","wifi"]},{"name":"sort_by_alpha","tags":["alphabet","alphabetize","az","by alpha","character","font","letter","list","order","organize","sort","symbol","text","type"]},{"name":"theaters","tags":["film","movie","movies","show","showtimes","theater","theaters","watch"]},{"name":"cloud_done","tags":["app","application","approve","backup","check","cloud","complete","connection","done","drive","files","folders","internet","mark","network","ok","select","sky","storage","tick","upload","validate","verified","yes"]},{"name":"local_parking","tags":["alphabet","auto","car","character","font","garage","letter","local","park","parking","symbol","text","type","vehicle"]},{"name":"view_agenda","tags":["agenda","cards","design","format","grid","layout","stacked","view","website"]},{"name":"mark_email_unread","tags":["check","circle","email","envelop","letter","mail","mark","message","note","notification","send","unread"]},{"name":"local_florist","tags":["florist","flower","local","shop"]},{"name":"connect_without_contact","tags":["communicating","connect","contact","distance","people","signal","social","socialize","without"]},{"name":"thumb_down_off_alt","tags":["disabled","dislike","down","enabled","favorite","filled","fingers","gesture","hand","hands","like","off","offline","on","rank","ranking","rate","rating","slash","thumb"]},{"name":"sentiment_neutral","tags":["emotionless","emotions","expressions","face","feelings","fine","indifference","mood","neutral","okay","person","sentiment","survey"]},{"name":"call_end","tags":["call","cell","contact","device","end","hardware","mobile","phone","telephone"]},{"name":"subdirectory_arrow_right","tags":["arrow","directory","down","navigation","right","sub","subdirectory"]},{"name":"diamond","tags":["diamond","fashion","gems","jewelry","logo","retail","valuable","valuables"]},{"name":"podcasts","tags":["broadcast","casting","network","podcasts","signal","transmitting","wireless"]},{"name":"monitor_heart","tags":["baseline","device","ecc","ecg","fitness","health","heart","medical","monitor","track"]},{"name":"all_inclusive","tags":["all","endless","forever","inclusive","infinity","loop","mobius","neverending","strip","sustainability","sustainable"]},{"name":"wc","tags":["bathroom","closet","female","male","man","restroom","room","wash","water","wc","women"]},{"name":"grass","tags":["backyard","fodder","grass","ground","home","lawn","plant","turf","yard"]},{"name":"important_devices","tags":["Android","OS","desktop","devices","hardware","iOS","important","mobile","monitor","phone","star","tablet","web"]},{"name":"back_hand","tags":["back","fingers","gesture","hand","raised"]},{"name":"hiking","tags":["backpacking","bag","climbing","duffle","hiking","mountain","social","sports","stick","trail","travel","walking"]},{"name":"masks","tags":["air","cover","covid","face","hospital","masks","medical","pollution","protection","respirator","sick","social"]},{"name":"waving_hand","tags":["bye","fingers","gesture","goodbye","greetings","hand","hello","palm","wave","waving"]},{"name":"architecture","tags":["architecture","art","compass","design","draw","drawing","engineering","geometric","tool"]},{"name":"local_post_office","tags":["delivery","email","envelop","letter","local","mail","message","office","package","parcel","post","postal","send","stamp"]},{"name":"functions","tags":["average","calculate","count","custom","doc","edit","editing","editor","functions","math","sheet","spreadsheet","style","sum","text","type","writing"]},{"name":"directions","tags":["arrow","directions","maps","right","route","sign","traffic"]},{"name":"money","tags":["100","bill","card","cash","coin","commerce","cost","credit","currency","digit","dollars","finance","money","number","online","pay","payment","price","shopping","symbol"]},{"name":"unpublished","tags":["approve","check","circle","complete","disabled","done","enabled","mark","off","ok","on","select","slash","tick","unpublished","validate","verified","yes"]},{"name":"notifications_off","tags":["active","alarm","alert","bell","chime","disabled","enabled","notifications","notify","off","offline","on","reminder","ring","slash","sound"]},{"name":"airport_shuttle","tags":["airport","automobile","car","cars","commercial","delivery","direction","maps","mini","public","shuttle","transport","transportation","travel","truck","van","vehicle"]},{"name":"insert_link","tags":["add","attach","clip","file","insert","link","mail","media"]},{"name":"thumb_down_alt","tags":["bad","decline","disapprove","dislike","down","feedback","hate","negative","no","reject","social","thumb","veto","vote"]},{"name":"two_wheeler","tags":["automobile","bike","car","cars","direction","maps","motorcycle","public","scooter","sport","transportation","travel","two wheeler","vehicle"]},{"name":"nightlight","tags":["dark","disturb","mode","moon","night","nightlight","sleep"]},{"name":"mic_none","tags":["hear","hearing","mic","microphone","noise","none","record","sound","voice"]},{"name":"keyboard_double_arrow_down","tags":["arrow","arrows","direction","double","down","multiple","navigation"]},{"name":"invert_colors","tags":["colors","drop","droplet","edit","editing","hue","invert","inverted","palette","tone","water"]},{"name":"clear_all","tags":["all","clear","doc","document","format","lines","list"]},{"name":"mouse","tags":["click","computer","cursor","device","hardware","mouse","wireless"]},{"name":"mode_edit_outline","tags":["compose","create","draft","draw","edit","mode","outline","pen","pencil","write"]},{"name":"open_in_browser","tags":["arrow","browser","in","open","site","up","web","website","window"]},{"name":"insert_invitation","tags":["calendar","date","day","event","insert","invitation","mark","month","range","remember","reminder","today","week"]},{"name":"fast_rewind","tags":["back","control","fast","media","music","play","rewind","speed","time","tv","video"]},{"name":"opacity","tags":["color","drop","droplet","hue","invert","inverted","opacity","palette","tone","water"]},{"name":"video_camera_front","tags":["account","camera","face","front","human","image","people","person","photo","photography","picture","profile","user","video"]},{"name":"commute","tags":["automobile","car","commute","direction","maps","public","train","transportation","trip","vehicle"]},{"name":"addchart","tags":["+","addchart","analytics","bar","bars","chart","data","diagram","graph","infographic","measure","metrics","new","plus","statistics","symbol","tracking"]},{"name":"no_accounts","tags":["account","accounts","avatar","disabled","enabled","face","human","no","off","offline","on","people","person","profile","slash","thumbnail","unavailable","unidentifiable","unknown","user"]},{"name":"coffee","tags":["beverage","coffee","cup","drink","mug","plate","set","tea"]},{"name":"luggage","tags":["airport","bag","baggage","carry","flight","hotel","luggage","on","suitcase","travel","trip"]},{"name":"workspaces","tags":["circles","collaboration","dot","filled","group","outline","space","team","work","workspaces"]},{"name":"child_care","tags":["babies","baby","care","child","children","face","infant","kids","newborn","toddler","young"]},{"name":"sports_score","tags":["destination","flag","goal","score","sports"]},{"name":"library_music","tags":["add","album","collection","library","music","song","sounds"]},{"name":"history_toggle_off","tags":["clock","date","history","off","schedule","time","toggle"]},{"name":"system_update_alt","tags":["arrow","down","download","export","system","update"]},{"name":"access_time","tags":[]},{"name":"rotate_right","tags":["around","arrow","direction","inprogress","load","loading refresh","renew","right","rotate","turn"]},{"name":"color_lens","tags":["art","color","lens","paint","pallet"]},{"name":"grid_on","tags":["collage","disabled","enabled","grid","image","layout","off","on","slash","view"]},{"name":"crop_free","tags":["adjust","adjustments","crop","edit","editing","focus","frame","free","image","photo","photos","settings","size","zoom"]},{"name":"cloud_queue","tags":["cloud","connection","internet","network","queue","sky","upload"]},{"name":"keyboard_voice","tags":["keyboard","mic","microphone","noise","record","recorder","speaker","voice"]},{"name":"format_align_left","tags":["align","alignment","doc","edit","editing","editor","format","left","sheet","spreadsheet","text","type","writing"]},{"name":"view_week","tags":["bars","columns","design","format","grid","layout","view","website","week"]},{"name":"real_estate_agent","tags":["agent","architecture","broker","estate","hand","home","house","loan","mortgage","property","real","residence","residential","sales","social"]},{"name":"horizontal_rule","tags":["gmail","horizontal","line","novitas","rule"]},{"name":"topic","tags":["data","doc","document","drive","file","folder","sheet","slide","storage","topic"]},{"name":"shower","tags":["bath","bathroom","closet","home","house","place","plumbing","room","shower","sprinkler","wash","water","wc"]},{"name":"format_italic","tags":["alphabet","character","doc","edit","editing","editor","font","format","italic","letter","sheet","spreadsheet","style","symbol","text","type","writing"]},{"name":"traffic","tags":["direction","light","maps","signal","street","traffic"]},{"name":"add_business","tags":["+","add","bill","building","business","card","cash","coin","commerce","company","credit","currency","dollars","market","money","new","online","pay","payment","plus","shop","shopping","store","storefront","symbol"]},{"name":"electrical_services","tags":["charge","cord","electric","electrical","plug","power","services","wire"]},{"name":"timelapse","tags":["duration","motion","photo","time","timelapse","timer","video"]},{"name":"youtube_searched_for","tags":["arrow","back","backwards","find","glass","history","inprogress","load","loading","look","magnify","magnifying","refresh","renew","restore","reverse","rotate","search","see","youtube"]},{"name":"front_hand","tags":["fingers","front","gesture","hand","hello","palm","stop"]},{"name":"yard","tags":["backyard","flower","garden","home","house","nature","pettle","plants","yard"]},{"name":"tour","tags":["destination","flag","places","tour","travel","visit"]},{"name":"factory","tags":["factory","industry","manufacturing","warehouse"]},{"name":"developer_board","tags":["board","chip","computer","developer","development","hardware","microchip","processor"]},{"name":"more","tags":["3","archive","bookmark","dots","etc","favorite","indent","label","more","remember","save","stamp","sticker","tab","tag","three"]},{"name":"star_purple500","tags":["500","best","bookmark","favorite","highlight","purple","ranking","rate","rating","save","star","toggle"]},{"name":"format_color_fill","tags":["bucket","color","doc","edit","editing","editor","fill","format","paint","sheet","spreadsheet","style","text","type","writing"]},{"name":"beach_access","tags":["access","beach","places","summer","sunny","umbrella"]},{"name":"local_bar","tags":["alcohol","bar","bottle","club","cocktail","drink","food","liquor","local","wine"]},{"name":"add_link","tags":["add","attach","clip","link","new","plus","symbol"]},{"name":"landscape","tags":["image","landscape","mountain","mountains","nature","photo","photography","picture"]},{"name":"slideshow","tags":["movie","photos","play","slideshow","square","video","view"]},{"name":"stream","tags":["cast","connected","feed","live","network","signal","stream","wireless"]},{"name":"videocam_off","tags":["cam","camera","conference","disabled","enabled","film","filming","hardware","image","motion","off","offline","on","picture","slash","video","videography"]},{"name":"directions_boat","tags":["automobile","boat","car","cars","direction","directions","ferry","maps","public","transportation","vehicle"]},{"name":"download_done","tags":["arrow","arrows","check","done","down","download","downloads","drive","install","installed","ok","tick","upload"]},{"name":"volume_down","tags":["audio","control","down","music","sound","speaker","tv","volume"]},{"name":"alt_route","tags":["alt","alternate","alternative","arrows","direction","maps","navigation","options","other","route","routes","split","symbol"]},{"name":"mood_bad","tags":["bad","disappointment","dislike","emoji","emotions","expressions","face","feelings","mood","person","rating","social","survey","unhappiness","unhappy","unpleased","unsmile","unsmiling"]},{"name":"vaccines","tags":["aid","covid","doctor","drug","emergency","hospital","immunity","injection","medical","medication","medicine","needle","pharmacy","sick","syringe","vaccination","vaccines","vial"]},{"name":"dialpad","tags":["buttons","call","contact","device","dial","dialpad","dots","mobile","numbers","pad","phone"]},{"name":"route","tags":["directions","maps","path","route","sign","traffic"]},{"name":"hide_source","tags":["circle","disabled","enabled","hide","off","offline","on","shape","slash","source"]},{"name":"bookmark_added","tags":["added","approve","bookmark","check","complete","done","favorite","mark","ok","remember","save","select","tick","validate","verified","yes"]},{"name":"mark_as_unread","tags":["as","envelop","letter","mail","mark","post","postal","read","receive","send","unread"]},{"name":"plagiarism","tags":["doc","document","find","glass","look","magnifying","page","paper","plagiarism","search","see"]},{"name":"turned_in","tags":["archive","bookmark","favorite","in","label","library","read","reading","remember","ribbon","save","tag","turned"]},{"name":"settings_input_antenna","tags":["airplay","antenna","arrows","cast","computer","connect","connection","connectivity","dots","input","internet","network","screencast","settings","stream","wifi","wireless"]},{"name":"shop","tags":["bag","bill","buy","card","cart","cash","coin","commerce","credit","currency","dollars","google","money","online","pay","payment","play","shop","shopping","store"]},{"name":"pool","tags":["athlete","athletic","beach","body","entertainment","exercise","hobby","human","ocean","people","person","places","pool","sea","sports","swim","swimming","water"]},{"name":"search_off","tags":["cancel","close","disabled","enabled","find","glass","look","magnify","magnifying","off","on","search","see","slash","stop","x"]},{"name":"approval","tags":["apply","approval","approvals","approve","certificate","certification","disapproval","drive","file","impression","ink","mark","postage","stamp"]},{"name":"currency_rupee","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","money","online","pay","payment","price","rupee","shopping","symbol"]},{"name":"power","tags":["charge","cord","electric","electrical","outlet","plug","power"]},{"name":"collections_bookmark","tags":["album","archive","bookmark","collections","favorite","gallery","label","library","read","reading","remember","ribbon","save","stack","tag"]},{"name":"not_started","tags":["circle","media","not","pause","play","started","video"]},{"name":"pedal_bike","tags":["automobile","bicycle","bike","car","cars","direction","human","maps","pedal","public","route","scooter","transportation","vehicle","vespa"]},{"name":"water","tags":["aqua","beach","lake","ocean","river","water","waves","weather"]},{"name":"router","tags":["box","cable","connection","hardware","internet","network","router","signal","wifi"]},{"name":"flight_land","tags":["airport","arrival","arriving","flight","fly","land","landing","plane","transportation","travel"]},{"name":"shopping_cart_checkout","tags":["arrow","cart","cash","checkout","coin","commerce","currency","dollars","money","online","pay","payment","right","shopping"]},{"name":"agriculture","tags":["agriculture","automobile","car","cars","cultivation","farm","harvest","maps","tractor","transport","travel","truck","vehicle"]},{"name":"where_to_vote","tags":["approve","ballot","check","complete","destination","direction","done","location","maps","mark","ok","pin","place","poll","select","stop","tick","to","validate election","verified","vote","where","yes"]},{"name":"beenhere","tags":["approve","archive","beenhere","bookmark","check","complete","done","favorite","label","library","mark","ok","read","reading","remember","ribbon","save","select","tag","tick","validate","verified","yes"]},{"name":"add_comment","tags":["+","add","bubble","chat","comment","communicate","feedback","message","new","plus","speech","symbol"]},{"name":"copy_all","tags":["all","content","copy","cut","doc","document","file","multiple","page","paper","past"]},{"name":"dynamic_feed","tags":["'mail_outline'","'markunread'. Keep 'mail' and remove others.","Duplicate of 'email'"]},{"name":"videogame_asset","tags":["asset","console","controller","device","game","gamepad","gaming","playstation","video"]},{"name":"move_to_inbox","tags":["archive","arrow","down","email","envelop","inbox","incoming","letter","mail","message","move to","send"]},{"name":"crop_square","tags":["adjust","adjustments","app","application","area","components","crop","design","edit","editing","expand","frame","image","images","interface","open","photo","photos","rectangle","screen","settings","shape","shapes","site","size","square","ui","ux","web","website","window"]},{"name":"recent_actors","tags":["account","actors","avatar","card","cards","carousel","face","human","layers","list","people","person","profile","recent","thumbnail","user"]},{"name":"emoji_nature","tags":["animal","bee","bug","daisy","emoji","flower","insect","ladybug","nature","petals","spring","summer"]},{"name":"cloud_off","tags":["app","application","backup","cloud","connection","disabled","drive","enabled","files","folders","internet","network","off","offline","on","sky","slash","storage","upload"]},{"name":"panorama_fish_eye","tags":["angle","circle","eye","fish","image","panorama","photo","photography","picture","wide"]},{"name":"lens","tags":["circle","full","geometry","lens","moon"]},{"name":"360","tags":["360","arrow","av","camera","direction","rotate","rotation","vr"]},{"name":"share_location","tags":["destination","direction","gps","location","maps","pin","place","share","stop","tracking"]},{"name":"assignment_late","tags":["!","alert","assignment","attention","caution","clipboard","danger","doc","document","error","exclamation","important","late","mark","notification","symbol","warning"]},{"name":"switch_account","tags":["account","choices","face","human","multiple","options","people","person","profile","social","switch","user"]},{"name":"looks_two","tags":["2","digit","looks","numbers","square","symbol"]},{"name":"do_not_disturb","tags":["cancel","close","denied","deny","disturb","do","remove","silence","stop"]},{"name":"donut_small","tags":["analytics","chart","data","diagram","donut","graph","infographic","inprogress","measure","metrics","pie","small","statistics","tracking"]},{"name":"saved_search","tags":["find","glass","important","look","magnify","magnifying","marked","saved","search","see","star"]},{"name":"contactless","tags":["bluetooth","cash","connect","connection","connectivity","contact","contactless","credit","device","finance","pay","payment","signal","transaction","wifi","wireless"]},{"name":"highlight_alt","tags":["alt","arrow","box","click","cursor","draw","focus","highlight","pointer","select","selection","target"]},{"name":"assignment_return","tags":["arrow","assignment","back","clipboard","doc","document","left","retun"]},{"name":"kitchen","tags":["appliance","cold","food","fridge","home","house","ice","kitchen","places","refrigerator","storage"]},{"name":"warehouse","tags":["garage","industry","manufacturing","storage","warehouse"]},{"name":"liquor","tags":["alcohol","bar","bottle","club","cocktail","drink","food","liquor","party","store","wine"]},{"name":"gpp_maybe","tags":["!","alert","attention","caution","certified","danger","error","exclamation","gpp","important","mark","maybe","notification","privacy","private","protect","protection","security","shield","sim","symbol","verified","warning"]},{"name":"settings_input_component","tags":["audio","av","cable","cables","component","connect","connection","connectivity","input","internet","plug","points","settings","video","wifi"]},{"name":"waves","tags":["beach","lake","ocean","pool","river","sea","swim","water","wave","waves"]},{"name":"hotel_class","tags":["achievement","bookmark","class","favorite","highlight","hotel","important","marked","rank","ranking","rate","rating","reward","save","saved","shape","special","star"]},{"name":"web_asset","tags":["-website","app","application desktop","asset","browser","design","download","image","interface","internet","layout","screen","site","ui","ux","video","web","website","window","www"]},{"name":"view_carousel","tags":["cards","carousel","design","format","grid","layout","view","website"]},{"name":"anchor","tags":["anchor","google","logo"]},{"name":"filter_alt_off","tags":["alt","disabled","edit","filter","funnel","off","offline","options","refine","sift","slash"]},{"name":"balance","tags":["balance","equal","equity","impartiality","justice","parity","stability. equilibrium","steadiness","symmetry"]},{"name":"view_quilt","tags":["design","format","grid","layout","quilt","square","squares","stacked","view","website"]},{"name":"library_add_check","tags":["add","approve","check","collection","complete","done","layers","library","mark","multiple","music","ok","select","stacked","tick","validate","verified","video","yes"]},{"name":"queue_music","tags":["collection","list","music","playlist","queue"]},{"name":"casino","tags":["casino","dice","dots","entertainment","gamble","gambling","game","games","luck","places"]},{"name":"hearing","tags":["accessibility","accessible","aid","ear","handicap","hearing","help","impaired","listen","sound","volume"]},{"name":"phone_enabled","tags":["call","cell","contact","device","enabled","hardware","mobile","phone","telephone"]},{"name":"linear_scale","tags":["app","application","components","design","interface","layout","linear","measure","menu","scale","screen","site","slider","ui","ux","web","website","window"]},{"name":"holiday_village","tags":["architecture","beach","camping","cottage","estate","holiday","home","house","lake","lodge","maps","place","real","residence","residential","stay","traveling","vacation","village"]},{"name":"turned_in_not","tags":["archive","bookmark","favorite","in","label","library","not","read","reading","remember","ribbon","save","tag","turned"]},{"name":"sync_problem","tags":["!","360","alert","around","arrow","arrows","attention","caution","danger","direction","error","exclamation","important","inprogress","load","loading refresh","mark","notification","problem","renew","rotate","symbol","sync","turn","warning"]},{"name":"start","tags":["arrow","keyboard","next","right","start"]},{"name":"all_inbox","tags":["Inbox","all","delivered","delivery","email","mail","message","send"]},{"name":"mediation","tags":["arrow","arrows","direction","dots","mediation","right"]},{"name":"edit_off","tags":["compose","create","disabled","draft","edit","editing","enabled","input","new","off","offline","on","pen","pencil","slash","write","writing"]},{"name":"emergency","tags":["asterisk","clinic","emergency","health","hospital","maps","medical","symbol"]},{"name":"settings_remote","tags":["bluetooth","connection","connectivity","device","remote","settings","signal","wifi","wireless"]},{"name":"drive_file_move","tags":["arrow","data","doc","document","drive","file","folder","move","right","sheet","slide","storage"]},{"name":"fit_screen","tags":["enlarge","fit","format","layout","reduce","scale","screen","size"]},{"name":"hourglass_full","tags":["countdown","full","hourglass","loading","minutes","time","wait","waiting"]},{"name":"nights_stay","tags":["climate","cloud","crescent","dark","lunar","mode","moon","nights","phases","silence","silent","sky","stay","time","weather"]},{"name":"pause_circle_filled","tags":["circle","control","controls","filled","media","music","pause","video"]},{"name":"catching_pokemon","tags":["catching","go","pokemon","pokestop","travel"]},{"name":"king_bed","tags":["bed","bedroom","double","furniture","home","hotel","house","king","night","pillows","queen","rest","room","sleep"]},{"name":"flaky","tags":["approve","check","close","complete","contrast","done","exit","flaky","mark","no","ok","options","select","stop","tick","verified","x","yes"]},{"name":"format_size","tags":["alphabet","character","color","doc","edit","editing","editor","fill","font","format","letter","paint","sheet","size","spreadsheet","style","symbol","text","type","writing"]},{"name":"interests","tags":["circle","heart","interests","shapes","social","square","triangle"]},{"name":"stacked_line_chart","tags":["analytics","chart","data","diagram","graph","infographic","line","measure","metrics","stacked","statistics","tracking"]},{"name":"unarchive","tags":["archive","arrow","inbox","mail","store","unarchive","undo","up"]},{"name":"subtitles","tags":["accessible","caption","cc","character","closed","decoder","language","media","movies","subtitle","subtitles","tv"]},{"name":"toll","tags":["bill","booth","car","card","cash","coin","commerce","credit","currency","dollars","highway","money","online","pay","payment","ticket","toll"]},{"name":"keyboard_double_arrow_up","tags":["arrow","arrows","direction","double","multiple","navigation","up"]},{"name":"time_to_leave","tags":["automobile","car","cars","destination","direction","drive","estimate","eta","maps","public","transportation","travel","trip","vehicle"]},{"name":"location_searching","tags":["destination","direction","location","maps","pin","place","pointer","searching","stop","tracking"]},{"name":"cable","tags":["cable","connect","connection","device","electronics","usb","wire"]},{"name":"moving","tags":["arrow","direction","moving","navigation","travel","up"]},{"name":"remove_shopping_cart","tags":["card","cart","cash","checkout","coin","commerce","credit","currency","disabled","dollars","enabled","off","on","online","pay","payment","remove","shopping","slash","tick"]},{"name":"cast_for_education","tags":["Android","OS","airplay","cast","chrome","connect","desktop","device","display","education","for","hardware","iOS","learning","lessons teaching","mac","monitor","screen","screencast","streaming","television","tv","web","window","wireless"]},{"name":"fiber_new","tags":["alphabet","character","fiber","font","letter","network","new","symbol","text","type"]},{"name":"format_underlined","tags":["alphabet","character","doc","edit","editing","editor","font","format","letter","line","sheet","spreadsheet","style","symbol","text","type","under","underlined","writing"]},{"name":"pause_circle_outline","tags":["circle","control","controls","media","music","outline","pause","video"]},{"name":"mark_chat_unread","tags":["bubble","chat","circle","comment","communicate","mark","message","notification","speech","unread"]},{"name":"insert_comment","tags":["add","bubble","chat","comment","feedback","insert","message"]},{"name":"cameraswitch","tags":["arrows","camera","cameraswitch","flip","rotate","swap","switch","view"]},{"name":"rocket","tags":["rocket","space","spaceship"]},{"name":"local_airport","tags":["air","airplane","airport","flight","plane","transportation","travel","trip"]},{"name":"lock_clock","tags":["clock","date","lock","locked","password","privacy","private","protection","safety","schedule","secure","security","time"]},{"name":"device_hub","tags":["Android","OS","circle","computer","desktop","device","hardware","hub","iOS","laptop","mobile","monitor","phone","square","tablet","triangle","watch","wearable","web"]},{"name":"filter_vintage","tags":["edit","editing","effect","filter","flower","image","images","photography","picture","pictures","vintage"]},{"name":"sailing","tags":["boat","entertainment","fishing","hobby","ocean","sailboat","sailing","sea","social sports","travel","water"]},{"name":"roofing","tags":["architecture","building","chimney","construction","estate","home","house","real","residence","residential","roof","roofing","service","shelter"]},{"name":"settings_voice","tags":["mic","microphone","record","recorder","settings","speaker","voice"]},{"name":"swap_horizontal_circle","tags":["arrow","arrows","back","circle","forward","horizontal","swap"]},{"name":"add_location_alt","tags":["+","add","alt","destination","direction","location","maps","new","pin","place","plus","stop","symbol"]},{"name":"room_service","tags":["alert","bell","delivery","hotel","notify","room","service"]},{"name":"content_paste_search","tags":["clipboard","content","doc","document","file","find","paste","search","trace","track"]},{"name":"reply_all","tags":["all","arrow","backward","group","left","mail","message","multiple","reply","send","share"]},{"name":"compost","tags":["bio","compost","compostable","decomposable","decompose","eco","green","leaf","leafs","nature","organic","plant","recycle","sustainability","sustainable"]},{"name":"bubble_chart","tags":["analytics","bar","bars","bubble","chart","data","diagram","graph","infographic","measure","metrics","statistics","tracking"]},{"name":"compare","tags":["adjust","adjustment","compare","edit","editing","edits","enhance","fix","image","images","photo","photography","photos","scan","settings"]},{"name":"money_off","tags":["bill","card","cart","cash","coin","commerce","credit","currency","disabled","dollars","enabled","money","off","on","online","pay","payment","shopping","slash","symbol"]},{"name":"file_open","tags":["arrow","doc","document","drive","file","left","open","page","paper"]},{"name":"filter_drama","tags":["cloud","drama","edit","editing","effect","filter","image","photo","photography","picture","sky camera"]},{"name":"shortcut","tags":["arrow","direction","forward","right","shortcut"]},{"name":"view_sidebar","tags":["design","format","grid","layout","sidebar","view","web"]},{"name":"looks_3","tags":["3","digit","looks","numbers","square","symbol"]},{"name":"note","tags":["bookmark","message","note","paper"]},{"name":"vertical_align_bottom","tags":["align","alignment","arrow","bottom","doc","down","edit","editing","editor","sheet","spreadsheet","text","type","vertical","writing"]},{"name":"3p","tags":["3","3p","account","avatar","bubble","chat","comment","communicate","face","human","message","party","people","person","profile","speech","user"]},{"name":"online_prediction","tags":["bulb","connection","idea","light","network","online","prediction","signal","wireless"]},{"name":"cancel_presentation","tags":["cancel","close","device","exit","no","present","presentation","quit","remove","screen","slide","stop","website","window","x"]},{"name":"select_all","tags":["all","select","selection","square","tool"]},{"name":"event_seat","tags":["assign","assigned","chair","event","furniture","reservation","row","seat","section","sit"]},{"name":"window","tags":["close","glass","grid","home","house","interior","layout","outside","window"]},{"name":"av_timer","tags":["av","clock","countdown","duration","minutes","seconds","time","timer","watch"]},{"name":"album","tags":["album","artist","audio","bvb","cd","computer","data","disk","file","music","record","sound","storage","track"]},{"name":"local_dining","tags":["dining","eat","food","fork","knife","local","meal","restaurant","spoon"]},{"name":"headset","tags":["accessory","audio","device","ear","earphone","headphones","headset","listen","music","sound"]},{"name":"maps_ugc","tags":["+","add","bubble","comment","communicate","feedback","maps","message","new","plus","speech","symbol","ugc"]},{"name":"airplane_ticket","tags":["airplane","airport","boarding","flight","fly","maps","pass","ticket","transportation","travel"]},{"name":"vertical_split","tags":["design","format","grid","layout","paragraph","split","text","vertical","website","writing"]},{"name":"sports_basketball","tags":["athlete","athletic","ball","basketball","entertainment","exercise","game","hobby","social","sports"]},{"name":"next_plan","tags":["arrow","circle","next","plan","right"]},{"name":"drive_folder_upload","tags":["arrow","data","doc","document","drive","file","folder","sheet","slide","storage","up","upload"]},{"name":"pregnant_woman","tags":["baby","birth","body","female","human","lady","maternity","mom","mother","people","person","pregnant","women"]},{"name":"wallpaper","tags":["background","image","landscape","photo","photography","picture","wallpaper"]},{"name":"image_search","tags":["find","glass","image","landscape","look","magnify","magnifying","mountain","mountains","photo","photography","picture","search","see"]},{"name":"data_exploration","tags":["analytics","arrow","chart","data","diagram","exploration","graph","infographic","measure","metrics","statistics","tracking"]},{"name":"device_thermostat","tags":["celsius","device","fahrenheit","meter","temp","temperature","thermometer","thermostat"]},{"name":"healing","tags":["bandage","edit","editing","emergency","fix","healing","hospital","image","medicine"]},{"name":"laptop_mac","tags":["Android","OS","chrome","device","display","hardware","iOS","laptop","mac","monitor","screen","web","window"]},{"name":"height","tags":["arrow","color","doc","down","edit","editing","editor","fill","format","height","paint","sheet","spreadsheet","style","text","type","up","writing"]},{"name":"restore_from_trash","tags":["arrow","back","backwards","clock","date","history","refresh","renew","restore","reverse","rotate","schedule","time","turn"]},{"name":"radar","tags":["detect","military","near","network","position","radar","scan"]},{"name":"auto_awesome_motion","tags":["adjust","auto","awesome","collage","edit","editing","enhance","image","motion","photo","video"]},{"name":"file_download_done","tags":["arrow","arrows","check","done","down","download","downloads","drive","file","install","installed","tick","upload"]},{"name":"notification_add","tags":["+","active","add","alarm","alert","bell","chime","notification","notifications","notify","plus","reminder","ring","sound","symbol"]},{"name":"call_made","tags":["arrow","call","device","made","mobile"]},{"name":"camera_enhance","tags":["ai","artificial","automatic","automation","camera","custom","enhance","genai","important","intelligence","lens","magic","photo","photography","picture","quality","smart","spark","sparkle","special","star"]},{"name":"rotate_left","tags":["around","arrow","direction","inprogress","left","load","loading refresh","renew","rotate","turn"]},{"name":"local_taxi","tags":["automobile","cab","call","car","cars","direction","local","lyft","maps","public","taxi","transportation","uber","vehicle","yellow"]},{"name":"star_border_purple500","tags":["500","best","bookmark","border","favorite","highlight","outline","purple","ranking","rate","rating","save","star","toggle"]},{"name":"gpp_bad","tags":["bad","cancel","certified","close","error","exit","gpp","no","privacy","private","protect","protection","remove","security","shield","sim","stop","verified","x"]},{"name":"playlist_play","tags":["arrow","collection","list","music","play","playlist"]},{"name":"cast","tags":["Android","OS","airplay","cast","chrome","connect","desktop","device","display","hardware","iOS","mac","monitor","screen","screencast","streaming","television","tv","web","window","wireless"]},{"name":"vertical_align_top","tags":["align","alignment","arrow","doc","edit","editing","editor","sheet","spreadsheet","text","top","type","up","vertical","writing"]},{"name":"ramen_dining","tags":["breakfast","dining","dinner","drink","fastfood","food","lunch","meal","noodles","ramen","restaurant"]},{"name":"data_usage","tags":["analytics","chart","data","diagram","graph","infographic","measure","metrics","statistics","tracking","usage"]},{"name":"markunread_mailbox","tags":["deliver","envelop","letter","mail","mailbox","markunread","post","postal","postbox","receive","send","unread"]},{"name":"terminal","tags":["application","code","emulator","program","software","terminal"]},{"name":"screen_share","tags":["Android","OS","arrow","cast","chrome","device","display","hardware","iOS","laptop","mac","mirror","monitor","screen","share","steam","streaming","web","window"]},{"name":"center_focus_strong","tags":["camera","center","focus","image","lens","photo","photography","strong","zoom"]},{"name":"queue","tags":["add","collection","layers","list","multiple","music","playlist","queue","stack","stream","video"]},{"name":"games","tags":["adjust","arrow","arrows","control","controller","direction","games","gaming","left","move","right"]},{"name":"low_priority","tags":["arrange","arrow","backward","bottom","list","low","move","order","priority"]},{"name":"dynamic_form","tags":["bolt","code","dynamic","electric","fast","form","lightning","lists","questionnaire","thunderbolt"]},{"name":"tab","tags":["browser","computer","document","documents","folder","internet","tab","tabs","web","website","window","windows"]},{"name":"lock_reset","tags":["around","inprogress","load","loading refresh","lock","locked","password","privacy","private","protection","renew","rotate","safety","secure","security","turn"]},{"name":"room_preferences","tags":["building","door","doorway","entrance","gear","home","house","interior","office","open","preferences","room","settings"]},{"name":"crop","tags":["adjust","adjustments","area","crop","edit","editing","frame","image","images","photo","photos","rectangle","settings","size","square"]},{"name":"monitor_weight","tags":["body","device","diet","health","monitor","scale","smart","weight"]},{"name":"trip_origin","tags":["circle","departure","origin","trip"]},{"name":"calendar_view_week","tags":["calendar","date","day","event","format","grid","layout","month","schedule","today","view","week"]},{"name":"signal_wifi_4_bar","tags":["4","bar","cell","cellular","data","internet","mobile","network","phone","signal","wifi","wireless"]},{"name":"blur_on","tags":["blur","disabled","dots","edit","editing","effect","enabled","enhance","filter","off","on","slash"]},{"name":"view_stream","tags":["design","format","grid","layout","lines","list","stacked","stream","view","website"]},{"name":"radio","tags":["antenna","audio","device","frequency","hardware","listen","media","music","player","radio","signal","tune"]},{"name":"hail","tags":["body","hail","human","people","person","pick","public","stop","taxi","transportation"]},{"name":"do_disturb_on","tags":["cancel","close","denied","deny","disabled","disturb","do","enabled","off","on","remove","silence","slash","stop"]},{"name":"sensor_door","tags":["alarm","security","security system"]},{"name":"wb_incandescent","tags":["balance","bright","edit","editing","incandescent","light","lighting","setting","settings","white","wp"]},{"name":"local_drink","tags":["cup","drink","drop","droplet","liquid","local","park","water"]},{"name":"accessible_forward","tags":["accessibility","accessible","body","forward","handicap","help","human","people","person","wheelchair"]},{"name":"replay_circle_filled","tags":["arrow","arrows","circle","control","controls","filled","music","refresh","renew","repeat","replay","video"]},{"name":"local_printshop","tags":["draft","fax","ink","local","machine","office","paper","print","printer","printshop","send"]},{"name":"local_laundry_service","tags":["cleaning","clothing","dry","dryer","hotel","laundry","local","service","washer"]},{"name":"vpn_lock","tags":["earth","globe","lock","locked","network","password","privacy","private","protection","safety","secure","security","virtual","vpn","world"]},{"name":"schema","tags":["analytics","chart","data","diagram","flow","graph","infographic","measure","metrics","schema","statistics","tracking"]},{"name":"request_page","tags":["data","doc","document","drive","file","folder","folders","page","paper","request","sheet","slide","writing"]},{"name":"token","tags":["badge","hexagon","mark","shield","sign","symbol"]},{"name":"branding_watermark","tags":["branding","components","copyright","design","emblem","format","identity","interface","layout","logo","screen","site","stamp","ui","ux","watermark","web","website","window"]},{"name":"theater_comedy","tags":["broadway","comedy","event","movie","musical","places","show","standup","theater","tour","watch"]},{"name":"text_format","tags":["alphabet","character","font","format","letter","square A","style","symbol","text","type"]},{"name":"directions_bus_filled","tags":["automobile","bus","car","cars","direction","directions","filled","maps","public","transportation","vehicle"]},{"name":"remove_done","tags":["approve","check","complete","disabled","done","enabled","finished","mark","multiple","off","ok","on","remove","select","slash","tick","yes"]},{"name":"sports_bar","tags":["alcohol","bar","beer","drink","liquor","pint","places","pub","sports"]},{"name":"watch","tags":["Android","OS","ar","clock","gadget","iOS","time","vr","watch","wearables","web","wristwatch"]},{"name":"add_to_drive","tags":["add","app","application","backup","cloud","drive","files","folders","gdrive","google","recovery","shortcut","storage"]},{"name":"format_align_center","tags":["align","alignment","center","doc","edit","editing","editor","format","sheet","spreadsheet","text","type","writing"]},{"name":"settings_power","tags":["info","information","off","on","power","save","settings","shutdown"]},{"name":"local_pizza","tags":["drink","fastfood","food","local","meal","pizza"]},{"name":"add_alert","tags":["+","active","add","alarm","alert","bell","chime","new","notifications","notify","plus","reminder","ring","sound","symbol"]},{"name":"smart_button","tags":["action","ai","artificial","automatic","automation","button","components","composer","custom","function","genai","intelligence","interface","magic","site","smart","spark","sparkle","special","star","stars","ui","ux","web","website"]},{"name":"flare","tags":["bright","edit","editing","effect","flare","image","images","light","photography","picture","pictures","sun"]},{"name":"developer_mode","tags":["Android","OS","bracket","cell","code","developer","development","device","engineer","hardware","iOS","mobile","mode","phone","tablet"]},{"name":"call_split","tags":["arrow","call","device","mobile","split"]},{"name":"free_breakfast","tags":["beverage","breakfast","cafe","coffee","cup","drink","free","mug","tea"]},{"name":"auto_delete","tags":["auto","bin","can","clock","date","delete","garbage","remove","schedule","time","trash"]},{"name":"sports_kabaddi","tags":["athlete","athletic","body","combat","entertainment","exercise","fighting","game","hobby","human","kabaddi","people","person","social","sports","wrestle","wrestling"]},{"name":"face_retouching_natural","tags":["ai","artificial","automatic","automation","custom","edit","editing","effect","emoji","emotion","face","faces","genai","image","intelligence","magic","natural","photo","photography","retouch","retouching","settings","smart","spark","sparkle","star","tag"]},{"name":"not_listed_location","tags":["?","assistance","destination","direction","help","info","information","listed","location","maps","not","pin","place","punctuation","question mark","stop","support","symbol"]},{"name":"wb_cloudy","tags":["balance","cloud","cloudy","edit","editing","white","wp"]},{"name":"sports","tags":["athlete","athletic","blowing","coach","entertainment","exercise","game","hobby","instrument","referee","social","sound","sports","warning","whistle"]},{"name":"emoji_symbols","tags":["ampersand","character","emoji","hieroglyph","music","note","percent","sign","symbols"]},{"name":"bathtub","tags":["bath","bathing","bathroom","bathtub","home","hotel","human","person","shower","travel","tub"]},{"name":"forward_10","tags":["10","arrow","control","controls","digit","fast","forward","music","number","play","seconds","symbol","video"]},{"name":"tablet_mac","tags":["Android","OS","device","hardware","iOS","ipad","mobile","tablet mac","web"]},{"name":"mode_night","tags":["dark","disturb","lunar","mode","moon","night","sleep"]},{"name":"broken_image","tags":["broken","corrupt","error","image","landscape","mountain","mountains","photo","photography","picture","torn"]},{"name":"escalator_warning","tags":["body","child","escalator","human","kid","parent","people","person","warning"]},{"name":"assistant","tags":["ai","artificial","assistant","automatic","automation","bubble","chat","comment","communicate","custom","feedback","genai","intelligence","magic","message","recommendation","smart","spark","sparkle","speech","star","suggestion","twinkle"]},{"name":"cases","tags":["bag","baggage","briefcase","business","case","cases","purse","suitcase"]},{"name":"wifi_tethering","tags":["cell","cellular","connection","data","internet","mobile","network","phone","scan","service","signal","speed","tethering","wifi","wireless"]},{"name":"reduce_capacity","tags":["arrow","body","capacity","covid","decrease","down","human","people","person","reduce","social"]},{"name":"colorize","tags":["color","colorize","dropper","extract","eye","picker","tool"]},{"name":"save_as","tags":["compose","create","data","disk","document","draft","drive","edit","editing","file","floppy","input","multimedia","pen","pencil","save","storage","write","writing"]},{"name":"card_travel","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","membership","miles","money","online","pay","payment","travel","trip"]},{"name":"emoji_food_beverage","tags":["beverage","coffee","cup","drink","emoji","mug","plate","set","tea"]},{"name":"font_download","tags":["A","alphabet","character","download","font","letter","square","symbol","text","type"]},{"name":"outbox","tags":["box","mail","outbox","send","sent"]},{"name":"battery_std","tags":["battery","cell","charge","mobile","plus","power","standard","std"]},{"name":"sick","tags":["covid","discomfort","emotions","expressions","face","feelings","fever","flu","ill","mood","pain","person","sick","survey","upset"]},{"name":"add_location","tags":["+","add","destination","direction","location","maps","new","pin","place","plus","stop","symbol"]},{"name":"try","tags":["bookmark","bubble","chat","comment","communicate","favorite","feedback","highlight","important","marked","message","save","saved","shape","special","speech","star","try"]},{"name":"discount","tags":[]},{"name":"man","tags":["boy","gender","male","man","social","symbol"]},{"name":"running_with_errors","tags":["!","alert","attention","caution","danger","duration","error","errors","exclamation","important","mark","notification","process","processing","running","symbol","time","warning","with"]},{"name":"diversity_3","tags":["committee","diverse","diversity","family","friends","group","groups","humans","network","people","persons","social","team"]},{"name":"filter_none","tags":["filter","multiple","none","square","stack"]},{"name":"cloud_sync","tags":["app","application","around","backup","cloud","connection","drive","files","folders","inprogress","internet","load","loading refresh","network","renew","rotate","sky","storage","turn","upload"]},{"name":"bloodtype","tags":["blood","bloodtype","donate","droplet","emergency","hospital","medicine","negative","positive","type","water"]},{"name":"dinner_dining","tags":["breakfast","dining","dinner","food","fork","lunch","meal","restaurant","spaghetti","utensils"]},{"name":"transfer_within_a_station","tags":["a","arrow","arrows","body","direction","human","left","maps","people","person","public","right","route","station","stop","transfer","transportation","vehicle","walk","within"]},{"name":"weekend","tags":["chair","couch","furniture","home","living","lounge","relax","room","weekend"]},{"name":"child_friendly","tags":["baby","care","carriage","child","children","friendly","infant","kid","newborn","stroller","toddler","young"]},{"name":"offline_pin","tags":["approve","check","checkmark","circle","complete","done","mark","offline","ok","pin","select","tick","validate","verified","yes"]},{"name":"replay_10","tags":["10","arrow","arrows","control","controls","digit","music","number","refresh","renew","repeat","replay","symbol","ten","video"]},{"name":"brightness_4","tags":["4","brightness","circle","control","crescent","level","moon","screen","sun"]},{"name":"cruelty_free","tags":["animal","bunny","cruelty","eco","free","nature","rabbit","social","sustainability","sustainable","testing"]},{"name":"format_paint","tags":["brush","color","doc","edit","editing","editor","fill","format","paint","roller","sheet","spreadsheet","style","text","type","writing"]},{"name":"filter_center_focus","tags":["camera","center","dot","edit","filter","focus","image","photo","photography","picture"]},{"name":"area_chart","tags":["analytics","area","chart","data","diagram","graph","infographic","measure","metrics","statistics","tracking"]},{"name":"bakery_dining","tags":["bakery","bread","breakfast","brunch","croissant","dining","food"]},{"name":"emoji_transportation","tags":["architecture","automobile","building","car","cars","direction","emoji","estate","maps","place","public","real","residence","residential","shelter","transportation","travel","vehicle"]},{"name":"folder_special","tags":["bookmark","data","doc","document","drive","favorite","file","folder","highlight","important","marked","save","saved","shape","sheet","slide","special","star","storage"]},{"name":"door_front","tags":["closed","door","doorway","entrance","exit","front","home","house","way"]},{"name":"calendar_view_day","tags":["calendar","date","day","event","format","grid","layout","month","schedule","today","view","week"]},{"name":"legend_toggle","tags":["analytics","chart","data","diagram","graph","infographic","legend","measure","metrics","monitoring","stackdriver","statistics","toggle","tracking"]},{"name":"light","tags":["bulb","ceiling","hanging","inside","interior","lamp","light","lighting","pendent","room"]},{"name":"find_replace","tags":["around","arrows","find","glass","inprogress","load","loading refresh","look","magnify","magnifying","renew","replace","rotate","search","see"]},{"name":"crop_original","tags":["adjust","adjustments","area","crop","edit","editing","frame","image","images","original","photo","photos","picture","settings","size"]},{"name":"rowing","tags":["activity","boat","body","canoe","human","people","person","row","rowing","sport","water"]},{"name":"enhanced_encryption","tags":["+","add","encryption","enhanced","lock","locked","new","password","plus","privacy","private","protection","safety","secure","security","symbol"]},{"name":"how_to_vote","tags":["ballot","election","how","poll","to","vote"]},{"name":"chrome_reader_mode","tags":["chrome","mode","read","reader","text"]},{"name":"auto_fix_normal","tags":["ai","artificial","auto","automatic","automation","custom","edit","erase","fix","genai","intelligence","magic","modify","smart","spark","sparkle","star","wand"]},{"name":"compress","tags":["arrow","arrows","collide","compress","pressure","push","together"]},{"name":"dehaze","tags":["adjust","dehaze","edit","editing","enhance","haze","image","lines","photo","photography","remove"]},{"name":"outlet","tags":["connect","connecter","electricity","outlet","plug","power"]},{"name":"desktop_mac","tags":["Android","OS","chrome","desktop","device","display","hardware","iOS","mac","monitor","screen","web","window"]},{"name":"nature_people","tags":["activity","body","forest","human","nature","outdoor","outside","park","people","person","tree","wilderness"]},{"name":"sports_tennis","tags":["athlete","athletic","ball","bat","entertainment","exercise","game","hobby","racket","social","sports","tennis"]},{"name":"forest","tags":["forest","jungle","nature","plantation","plants","trees","woodland"]},{"name":"upcoming","tags":["alarm","calendar","mail","message","notification","upcoming"]},{"name":"assignment_returned","tags":["arrow","assignment","clipboard","doc","document","down","returned"]},{"name":"cookie","tags":["biscuit","cookies","data","dessert","wafer"]},{"name":"fax","tags":["fax","machine","office","phone","send"]},{"name":"square","tags":["draw","four","shape quadrangle","sides","square"]},{"name":"density_medium","tags":["density","horizontal","lines","medium","rule","rules"]},{"name":"terrain","tags":["geography","landscape","mountain","terrain"]},{"name":"settings_brightness","tags":["brightness","dark","filter","light","mode","setting","settings"]},{"name":"attach_email","tags":["attach","attachment","clip","compose","email","envelop","letter","link","mail","message","send"]},{"name":"photo","tags":["image","mountain","mountains","photo","photography","picture"]},{"name":"http","tags":["alphabet","character","font","http","letter","symbol","text","transfer","type","url","website"]},{"name":"garage","tags":["automobile","automotive","car","cars","direction","garage","maps","transportation","travel","vehicle"]},{"name":"wine_bar","tags":["alcohol","bar","cocktail","cup","drink","glass","liquor","wine"]},{"name":"multiple_stop","tags":["arrows","directions","dots","left","maps","multiple","navigation","right","stop"]},{"name":"format_color_text","tags":["color","doc","edit","editing","editor","fill","format","paint","sheet","spreadsheet","style","text","type","writing"]},{"name":"gesture","tags":["drawing","finger","gesture","gestures","hand","motion"]},{"name":"heart_broken","tags":["break","broken","core","crush","health","heart","nucleus","split"]},{"name":"format_align_right","tags":["align","alignment","doc","edit","editing","editor","format","right","sheet","spreadsheet","text","type","writing"]},{"name":"transgender","tags":["female","gender","lgbt","male","neutral","social","symbol","transgender"]},{"name":"alarm_add","tags":["+","add","alarm","alert","bell","clock","countdown","date","new","notification","plus","schedule","symbol","time"]},{"name":"new_label","tags":["+","add","archive","bookmark","favorite","label","library","new","plus","read","reading","remember","ribbon","save","symbol","tag"]},{"name":"south_east","tags":["arrow","directional","down","east","maps","navigation","right","south"]},{"name":"backup_table","tags":["backup","drive","files folders","format","layout","stack","storage","table"]},{"name":"unsubscribe","tags":["cancel","close","email","envelop","letter","mail","message","newsletter","off","remove","send","subscribe","unsubscribe"]},{"name":"flash_off","tags":["bolt","disabled","electric","enabled","fast","flash","lightning","off","on","slash","thunderbolt"]},{"name":"elderly","tags":["body","cane","elderly","human","old","people","person","senior"]},{"name":"generating_tokens","tags":["access","ai","api","artificial","automatic","automation","coin","custom","genai","generating","intelligence","magic","smart","spark","sparkle","star","tokens"]},{"name":"spellcheck","tags":["a","alphabet","approve","character","check","font","letter","mark","ok","processor","select","spell","spellcheck","symbol","text","tick","type","word","write","yes"]},{"name":"auto_awesome_mosaic","tags":["adjust","auto","awesome","collage","edit","editing","enhance","image","mosaic","photo"]},{"name":"outdoor_grill","tags":["barbecue","bbq","charcoal","cooking","grill","home","house","outdoor","outside"]},{"name":"restore_page","tags":["arrow","data","doc","file","page","paper","refresh","restore","rotate","sheet","storage"]},{"name":"foundation","tags":["architecture","base","basis","building","construction","estate","foundation","home","house","real","residential"]},{"name":"credit_card_off","tags":["card","charge","commerce","cost","credit","disabled","enabled","finance","money","off","online","pay","payment","slash"]},{"name":"scatter_plot","tags":["analytics","bar","bars","chart","circles","data","diagram","dot","graph","infographic","measure","metrics","plot","scatter","statistics","tracking"]},{"name":"signal_cellular_4_bar","tags":["4","bar","cell","cellular","data","internet","mobile","network","phone","signal","speed","wifi","wireless"]},{"name":"add_moderator","tags":["+","add","certified","moderator","new","plus","privacy","private","protect","protection","security","shield","symbol","verified"]},{"name":"play_for_work","tags":["arrow","circle","down","google","half","play","work"]},{"name":"add_card","tags":["+","add","bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","money","new","online","pay","payment","plus","price","shopping","symbol"]},{"name":"app_settings_alt","tags":["Android","OS","app","applications","cell","device","gear","hardware","iOS","mobile","phone","setting","settings","tablet"]},{"name":"keyboard_tab","tags":["arrow","keyboard","left","next","right","tab"]},{"name":"wifi_protected_setup","tags":["around","arrow","arrows","protected","rotate","setup","wifi"]},{"name":"deck","tags":["chairs","deck","home","house","outdoors","outside","patio","social","terrace","umbrella","yard"]},{"name":"takeout_dining","tags":["box","container","delivery","dining","food","meal","restaurant","takeout"]},{"name":"tag_faces","tags":["emoji","emotion","faces","happy","satisfied","smile","tag"]},{"name":"brightness_6","tags":["6","brightness","circle","control","crescent","level","moon","screen","sun"]},{"name":"woman","tags":["female","gender","girl","lady","social","symbol","woman","women"]},{"name":"assistant_direction","tags":["assistant","destination","direction","location","maps","navigate","navigation","pin","place","right","stop"]},{"name":"brightness_5","tags":["5","brightness","circle","control","crescent","level","moon","screen","sun"]},{"name":"social_distance","tags":["6","apart","body","distance","ft","human","people","person","social","space"]},{"name":"free_cancellation","tags":["approve","calendar","cancel","cancellation","check","complete","date","day","done","event","exit","free","mark","month","no","ok","remove","schedule","select","stop","tick","validate","verified","x","yes"]},{"name":"subdirectory_arrow_left","tags":["arrow","directory","down","left","navigation","sub","subdirectory"]},{"name":"laptop_chromebook","tags":["Android","OS","chrome","chromebook","device","display","hardware","iOS","laptop","mac chromebook","monitor","screen","web","window"]},{"name":"format_list_numbered_rtl","tags":["align","alignment","digit","doc","edit","editing","editor","format","list","notes","number","numbered","rtl","sheet","spreadsheet","symbol","text","type","writing"]},{"name":"store_mall_directory","tags":["directory","mall","store"]},{"name":"settings_overscan","tags":["arrows","expand","image","photo","picture","scan","settings"]},{"name":"icecream","tags":["cream","dessert","food","ice","icecream","snack"]},{"name":"details","tags":["details","edit","editing","enhance","image","photo","photography","sharpen","triangle"]},{"name":"add_reaction","tags":["+","add","emoji","emotions","expressions","face","feelings","glad","happiness","happy","icon","icons","insert","like","mood","new","person","pleased","plus","smile","smiling","social","survey","symbol"]},{"name":"follow_the_signs","tags":["arrow","body","directional","follow","human","people","person","right","signs","social","the"]},{"name":"attribution","tags":["attribute","attribution","body","copyright","copywriter","human","people","person"]},{"name":"food_bank","tags":["architecture","bank","building","charity","eat","estate","food","fork","house","knife","meal","place","real","residence","residential","shelter","utensils"]},{"name":"closed_caption","tags":["accessible","alphabet","caption","cc","character","closed","decoder","font","language","letter","media","movies","subtitle","subtitles","symbol","text","tv","type"]},{"name":"gif","tags":["alphabet","animated","animation","bitmap","character","font","format","gif","graphics","interchange","letter","symbol","text","type"]},{"name":"phonelink","tags":["Android","OS","chrome","computer","connect","desktop","device","hardware","iOS","link","mac","mobile","phone","phonelink","sync","tablet","web","windows"]},{"name":"grain","tags":["dots","edit","editing","effect","filter","grain","image","images","photography","picture","pictures"]},{"name":"personal_injury","tags":["accident","aid","arm","bandage","body","broke","cast","fracture","health","human","injury","medical","patient","people","person","personal","sling","social"]},{"name":"flip_camera_android","tags":["android","camera","center","edit","editing","flip","image","mobile","orientation","rotate","turn"]},{"name":"museum","tags":["architecture","attraction","building","estate","event","exhibition","explore","local","museum","places","real","see","shop","store","tour"]},{"name":"north_west","tags":["arrow","directional","left","maps","navigation","north","up","west"]},{"name":"gite","tags":["architecture","estate","gite","home","hostel","house","maps","place","real","residence","residential","stay","traveling"]},{"name":"highlight","tags":["color","doc","edit","editing","editor","emphasize","fill","flash","format","highlight","light","paint","sheet","spreadsheet","style","text","type","writing"]},{"name":"brightness_1","tags":["1","brightness","circle","control","crescent","level","moon","screen"]},{"name":"plus_one","tags":["1","add","digit","increase","number","one","plus","symbol"]},{"name":"villa","tags":["architecture","beach","estate","home","house","maps","place","real","residence","residential","traveling","vacation stay","villa"]},{"name":"fmd_bad","tags":["!","alert","attention","bad","caution","danger","destination","direction","error","exclamation","fmd","important","location","maps","mark","notification","pin","place","symbol","warning"]},{"name":"flashlight_on","tags":["disabled","enabled","flash","flashlight","light","off","on","slash"]},{"name":"flip","tags":["edit","editing","flip","image","orientation","scan scanning"]},{"name":"nightlife","tags":["alcohol","bar","bottle","club","cocktail","dance","drink","food","glass","liquor","music","nightlife","note","wine"]},{"name":"present_to_all","tags":["all","arrow","present","presentation","screen","share","site","slides","to","web","website"]},{"name":"do_disturb","tags":["cancel","close","denied","deny","disturb","do","remove","silence","stop"]},{"name":"outbound","tags":["arrow","circle","directional","outbound","right","up"]},{"name":"local_pharmacy","tags":["911","aid","cross","emergency","first","hospital","local","medicine","pharmacy","places"]},{"name":"splitscreen","tags":["column","grid","layout","multitasking","row","screen","split","splitscreen","two"]},{"name":"waterfall_chart","tags":["analytics","bar","chart","data","diagram","graph","infographic","measure","metrics","statistics","tracking","waterfall"]},{"name":"switch_left","tags":["arrows","directional","left","navigation","switch","toggle"]},{"name":"domain_verification","tags":["app","application desktop","approve","check","complete","design","domain","done","interface","internet","layout","mark","ok","screen","select","site","tick","ui","ux","validate","verification","verified","web","website","window","www","yes"]},{"name":"fireplace","tags":["chimney","fire","fireplace","flame","home","house","living","pit","place","room","warm","winter"]},{"name":"video_settings","tags":["change","details","gear","info","information","options","play","screen","service","setting","settings","video","window"]},{"name":"disabled_visible","tags":["cancel","close","disabled","exit","eye","no","on","quit","remove","reveal","see","show","stop","view","visibility","visible"]},{"name":"network_wifi","tags":["cell","cellular","data","internet","mobile","network","phone","speed","wifi","wireless"]},{"name":"quickreply","tags":["bolt","bubble","chat","comment","communicate","fast","lightning","message","quick","quickreply","reply","speech","thunderbolt"]},{"name":"swap_vertical_circle","tags":["arrow","arrows","circle","down","swap","up","vertical"]},{"name":"format_align_justify","tags":["align","alignment","density","doc","edit","editing","editor","extra","format","justify","sheet","small","spreadsheet","text","type","writing"]},{"name":"settings_input_composite","tags":["component","composite","connection","connectivity","input","plug","points","settings"]},{"name":"loupe","tags":["+","add","details","focus","glass","loupe","magnifying","new","plus","symbol"]},{"name":"123","tags":["1","2","3","digit","number","symbol"]},{"name":"network_check","tags":["check","connect","connection","internet","meter","network","signal","speed","tick","wifi","wireless"]},{"name":"sms_failed","tags":["!","alert","attention","bubbles","caution","chat","communication","conversation","danger","error","exclamation","failed","feedback","important","mark","message","notification","service","sms","speech","symbol","warning"]},{"name":"cancel_schedule_send","tags":["cancel","email","mail","no","quit","remove","schedule","send","share","stop","x"]},{"name":"work_history","tags":["back","backwards","bag","baggage","briefcase","business","case","clock","date","history","job","pending","recent","schedule","suitcase","time","updates","work"]},{"name":"electric_bolt","tags":["bolt","electric","energy","fast","lightning","nest","thunderbolt"]},{"name":"view_day","tags":["cards","carousel","day","design","format","grid","layout","view","website"]},{"name":"night_shelter","tags":["architecture","bed","building","estate","homeless","house","night","place","real","shelter","sleep"]},{"name":"monitor","tags":["Android","OS","chrome","device","display","hardware","iOS","mac","monitor","screen","web","window"]},{"name":"clean_hands","tags":["bacteria","clean","disinfect","germs","gesture","hand","hands","sanitize","sanitizer"]},{"name":"mark_chat_read","tags":["approve","bubble","chat","check","comment","communicate","complete","done","mark","message","ok","read","select","sent","speech","tick","verified","yes"]},{"name":"comment_bank","tags":["archive","bank","bookmark","bubble","cchat","comment","communicate","favorite","label","library","message","remember","ribbon","save","speech","tag"]},{"name":"sim_card_download","tags":["arrow","camera","card","chip","device","down","download","memory","phone","sim","storage"]},{"name":"lan","tags":["computer","connection","data","internet","lan","network","service"]},{"name":"piano","tags":["instrument","keyboard","keys","music","musical","piano","social"]},{"name":"add_road","tags":["+","add","destination","direction","highway","maps","new","plus","road","stop","street","symbol","traffic"]},{"name":"add_ic_call","tags":["+","add","call","cell","contact","device","hardware","mobile","new","phone","plus","symbol","telephone"]},{"name":"rule_folder","tags":["approve","cancel","check","close","complete","data","doc","document","done","drive","exit","file","folder","mark","no","ok","remove","rule","select","sheet","slide","storage","tick","validate","verified","x","yes"]},{"name":"switch_access_shortcut","tags":["access","arrow","arrows","direction","navigation","new","north","shortcut","switch","symbol","up"]},{"name":"hardware","tags":["break","construction","hammer","hardware","nail","repair","tool"]},{"name":"line_weight","tags":["height","line","size","spacing","style","thickness","weight"]},{"name":"image_not_supported","tags":["disabled","enabled","image","landscape","mountain","mountains","not","off","on","photo","photography","picture","slash","supported"]},{"name":"flip_camera_ios","tags":["DISABLE_IOS","android","camera","disable_ios","edit","editing","flip","image","ios","mobile","orientation","rotate","turn"]},{"name":"phone_callback","tags":["arrow","call","callback","cell","contact","device","down","hardware","mobile","phone","telephone"]},{"name":"access_time_filled","tags":[]},{"name":"dining","tags":["cafe","cafeteria","cutlery","diner","dining","eat","eating","fork","room","spoon"]},{"name":"scale","tags":["measure","monitor","scale","weight"]},{"name":"airplanemode_active","tags":["active","airplane","airplanemode","flight","mode","on","signal"]},{"name":"set_meal","tags":["chopsticks","dinner","fish","food","lunch","meal","restaurant","set","teishoku"]},{"name":"mobile_friendly","tags":["Android","OS","approve","cell","check","complete","device","done","friendly","hardware","iOS","mark","mobile","ok","phone","select","tablet","tick","validate","verified","yes"]},{"name":"assured_workload","tags":["assured","compliance","confidential","federal","government","secure","sensitive regulatory","workload"]},{"name":"wallet","tags":[]},{"name":"merge_type","tags":["arrow","combine","direction","format","merge","text","type"]},{"name":"view_timeline","tags":["grid","layout","pattern","squares","timeline","view"]},{"name":"departure_board","tags":["automobile","board","bus","car","cars","clock","departure","maps","public","schedule","time","transportation","travel","vehicle"]},{"name":"event_repeat","tags":["around","calendar","date","day","event","inprogress","load","loading refresh","month","renew","rotate","schedule","turn"]},{"name":"sanitizer","tags":["bacteria","bottle","clean","covid","disinfect","germs","pump","sanitizer"]},{"name":"surfing","tags":["athlete","athletic","beach","body","entertainment","exercise","hobby","human","people","person","sea","social sports","sports","summer","surfing","water"]},{"name":"pix","tags":["bill","brazil","card","cash","commerce","credit","currency","finance","money","payment"]},{"name":"phonelink_ring","tags":["Android","OS","cell","connection","data","device","hardware","iOS","mobile","network","phone","phonelink","ring","service","signal","tablet","wireless"]},{"name":"display_settings","tags":["Android","OS","application","change","chrome","desktop","details","device","display","gear","hardware","iOS","info","information","mac","monitor","options","personal","screen","service","settings","web","window"]},{"name":"sports_motorsports","tags":["athlete","athletic","automobile","bike","drive","driving","entertainment","helmet","hobby","motorcycle","motorsports","protect","social","sports","vehicle"]},{"name":"horizontal_split","tags":["bars","format","horizontal","layout","lines","split","stacked"]},{"name":"view_comfy","tags":["comfy","grid","layout","pattern","squares","view"]},{"name":"polymer","tags":["emblem","logo","mark","polymer"]},{"name":"golf_course","tags":["athlete","athletic","ball","club","course","entertainment","flag","golf","golfer","golfing","hobby","hole","places","putt","sports"]},{"name":"batch_prediction","tags":["batch","bulb","idea","light","prediction"]},{"name":"filter_1","tags":["1","digit","edit","editing","effect","filter","image","images","multiple","number","photography","picture","pictures","settings","stack","symbol"]},{"name":"stay_current_portrait","tags":["Android","OS","current","device","hardware","iOS","mobile","phone","portrait","stay","tablet"]},{"name":"usb","tags":["cable","connection","device","usb","wire"]},{"name":"featured_play_list","tags":["collection","featured","highlighted","list","music","play","playlist","recommended"]},{"name":"data_object","tags":["brackets","code","coder","data","object","parentheses"]},{"name":"co_present","tags":["arrow","co-present","presentation","screen","share","site","slides","togather","web","website"]},{"name":"ev_station","tags":["automobile","car","cars","charging","electric","electricity","ev","maps","places","station","transportation","vehicle"]},{"name":"send_and_archive","tags":["archive","arrow","down","download","email","letter","mail","save","send","share"]},{"name":"send_to_mobile","tags":["Android","OS","arrow","device","export","forward","hardware","iOS","mobile","phone","right","send","share","tablet","to"]},{"name":"local_see","tags":["camera","lens","local","photo","photography","picture","see"]},{"name":"satellite_alt","tags":["alternative","artificial","communication","satellite","space","space station","television"]},{"name":"flatware","tags":["cafe","cafeteria","cutlery","diner","dining","eat","eating","fork","room","spoon"]},{"name":"speaker","tags":["box","electronic","loud","music","sound","speaker","stereo","system","video"]},{"name":"adb","tags":["adb","android","bridge","debug"]},{"name":"movie_creation","tags":["cinema","clapperboard","creation","film","movie","movies","slate","video"]},{"name":"picture_in_picture","tags":["crop","cropped","overlap","photo","picture","position","shape"]},{"name":"call_received","tags":["arrow","call","device","mobile","received"]},{"name":"battery_alert","tags":["!","alert","attention","battery","caution","cell","charge","danger","error","exclamation","important","mark","mobile","notification","power","symbol","warning"]},{"name":"system_update","tags":["Android","OS","arrow","arrows","cell","device","direction","down","download","hardware","iOS","install","mobile","phone","system","tablet","update"]},{"name":"webhook","tags":["api","developer","development","enterprise","software","webhook"]},{"name":"add_chart","tags":["+","add","analytics","bar","bars","chart","data","diagram","graph","infographic","measure","metrics","new","plus","statistics","symbol","tracking"]},{"name":"pan_tool_alt","tags":["fingers","gesture","hand","hands","human","move","pan","scan","stop","tool"]},{"name":"sports_handball","tags":["athlete","athletic","ball","body","entertainment","exercise","game","handball","hobby","human","people","person","social","sports"]},{"name":"electric_car","tags":["automobile","car","cars","electric","electricity","maps","transportation","travel","vehicle"]},{"name":"phone_forwarded","tags":["arrow","call","cell","contact","device","direction","forwarded","hardware","mobile","phone","right","telephone"]},{"name":"add_to_photos","tags":["add","collection","image","landscape","mountain","mountains","photo","photography","photos","picture","plus","to"]},{"name":"power_off","tags":["charge","cord","disabled","electric","electrical","enabled","off","on","outlet","plug","power","slash"]},{"name":"noise_control_off","tags":["audio","aware","cancel","cancellation","control","disabled","enabled","music","noise","note","off","offline","on","slash","sound"]},{"name":"code_off","tags":["brackets","code","css","develop","developer","disabled","enabled","engineer","engineering","html","off","on","platform","slash"]},{"name":"bookmark_remove","tags":["bookmark","delete","favorite","minus","remember","remove","ribbon","save","subtract"]},{"name":"screen_search_desktop","tags":["Android","OS","arrow","desktop","device","hardware","iOS","lock","monitor","rotate","screen","web"]},{"name":"panorama","tags":["angle","image","mountain","mountains","panorama","photo","photography","picture","view","wide"]},{"name":"settings_bluetooth","tags":["bluetooth","connect","connection","connectivity","device","settings","signal","symbol"]},{"name":"sports_baseball","tags":["athlete","athletic","ball","baseball","entertainment","exercise","game","hobby","social","sports"]},{"name":"festival","tags":["circus","event","festival","local","maps","places","tent","tour","travel"]},{"name":"lens_blur","tags":["blur","camera","dim","dot","effect","foggy","fuzzy","image","lens","photo","soften"]},{"name":"plumbing","tags":["build","construction","fix","handyman","plumbing","repair","tools","wrench"]},{"name":"toys","tags":["car","games","kids","toy","toys","windmill"]},{"name":"coffee_maker","tags":["appliances","beverage","coffee","cup","drink","machine","maker","mug"]},{"name":"edit_notifications","tags":["active","alarm","alert","bell","chime","compose","create","draft","edit","editing","input","new","notifications","notify","pen","pencil","reminder","ring","sound","write","writing"]},{"name":"personal_video","tags":["Android","OS","cam","chrome","desktop","device","hardware","iOS","mac","monitor","personal","television","tv","video","web","window"]},{"name":"animation","tags":["animation","circles","film","motion","movement","sequence","video"]},{"name":"bedtime","tags":["bedtime","nightime","sleep"]},{"name":"gamepad","tags":["buttons","console","controller","device","game","gamepad","gaming","playstation","video"]},{"name":"diversity_1","tags":["committee","diverse","diversity","family","friends","group","groups","heart","humans","network","people","persons","social","team"]},{"name":"center_focus_weak","tags":["camera","center","focus","image","lens","photo","photography","weak","zoom"]},{"name":"signal_wifi_statusbar_4_bar","tags":["4","bar","cell","cellular","data","internet","mobile","network","phone","signal","speed","statusbar","wifi","wireless"]},{"name":"manage_history","tags":["application","arrow","back","backwards","change","clock","date","details","gear","history","options","refresh","renew","reverse","rotate","schedule","settings","time","turn"]},{"name":"folder_zip","tags":["compress","data","doc","document","drive","file","folder","folders","open","sheet","slide","storage","zip"]},{"name":"flag_circle","tags":["circle","country","flag","goal","mark","nation","report","round","start"]},{"name":"south_west","tags":["arrow","directional","down","left","maps","navigation","south","west"]},{"name":"looks_4","tags":["4","digit","looks","numbers","square","symbol"]},{"name":"cloud_circle","tags":["app","application","backup","circle","cloud","connection","drive","files","folders","internet","network","sky","storage","upload"]},{"name":"format_shapes","tags":["alphabet","character","color","doc","edit","editing","editor","fill","font","format","letter","paint","shapes","sheet","spreadsheet","style","symbol","text","type","writing"]},{"name":"car_rental","tags":["automobile","car","cars","key","maps","rental","transportation","vehicle"]},{"name":"movie_filter","tags":["ai","artificial","automatic","automation","clapperboard","creation","custom","film","filter","genai","intelligence","magic","movie","movies","slate","smart","spark","sparkle","star","stars","video"]},{"name":"layers_clear","tags":["arrange","clear","delete","disabled","enabled","interaction","layers","maps","off","on","overlay","pages","slash"]},{"name":"phonelink_lock","tags":["Android","OS","cell","connection","device","erase","hardware","iOS","lock","locked","mobile","password","phone","phonelink","privacy","private","protection","safety","secure","security","tablet"]},{"name":"attractions","tags":["amusement","attractions","entertainment","ferris","fun","maps","park","places","wheel"]},{"name":"playlist_add_check_circle","tags":["add","album","artist","audio","cd","check","circle","collection","list","mark","music","playlist","record","sound","track"]},{"name":"hive","tags":["bee","honey","honeycomb"]},{"name":"no_photography","tags":["camera","disabled","enabled","image","no","off","on","photo","photography","picture","slash"]},{"name":"content_paste_go","tags":["clipboard","content","disabled","doc","document","enabled","file","go","on","paste","slash"]},{"name":"shop_two","tags":["add","arrow","buy","cart","google","play","purchase","shop","shopping","two"]},{"name":"edit_location","tags":["destination","direction","edit","location","maps","pen","pencil","pin","place","stop"]},{"name":"screen_rotation","tags":["Android","OS","arrow","device","hardware","iOS","mobile","phone","rotate","rotation","screen","tablet","turn"]},{"name":"numbers","tags":["digit","number","numbers","symbol"]},{"name":"sim_card","tags":["camera","card","chip","device","memory","phone","sim","storage"]},{"name":"control_camera","tags":["adjust","arrow","arrows","camera","center","control","direction","left","move","right"]},{"name":"blender","tags":["appliance","blender","cooking","electric","juicer","kitchen","machine","vitamix"]},{"name":"flip_to_front","tags":["arrange","arrangement","back","flip","format","front","layout","move","order","sort","to"]},{"name":"sports_volleyball","tags":["athlete","athletic","ball","entertainment","exercise","game","hobby","social","sports","volleyball"]},{"name":"stairs","tags":["down","staircase","stairs","up"]},{"name":"keyboard_alt","tags":["alt","computer","device","hardware","input","keyboard","keypad","letter","office","text","type"]},{"name":"crop_din","tags":["adjust","adjustments","area","crop","din","edit","editing","frame","image","images","photo","photos","rectangle","settings","size","square"]},{"name":"html","tags":["alphabet","brackets","character","code","css","develop","developer","engineer","engineering","font","html","letter","platform","symbol","text","type"]},{"name":"signal_wifi_statusbar_connected_no_internet_4","tags":["!","4","alert","attention","caution","cell","cellular","connected","danger","data","error","exclamation","important","internet","mark","mobile","network","no","notification","phone","signal","speed","statusbar","symbol","warning","wifi","wireless"]},{"name":"pivot_table_chart","tags":["analytics","arrow","arrows","bar","bars","chart","data","diagram","direction","drive","edit","editing","graph","grid","infographic","measure","metrics","pivot","rotate","sheet","statistics","table","tracking"]},{"name":"microwave","tags":["appliance","cooking","electric","heat","home","house","kitchen","machine","microwave"]},{"name":"folder_copy","tags":["content","copy","cut","data","doc","document","drive","duplicate","file","folder","folders","multiple","paste","sheet","slide","storage"]},{"name":"output","tags":[]},{"name":"gif_box","tags":["alphabet","animated","animation","bitmap","character","font","format","gif","graphics","interchange","letter","symbol","text","type"]},{"name":"voice_chat","tags":["bubble","cam","camera","chat","comment","communicate","facetime","feedback","message","speech","video","voice"]},{"name":"local_convenience_store","tags":["--","24","bill","building","business","card","cash","coin","commerce","company","convenience","credit","currency","dollars","local","maps","market","money","new","online","pay","payment","plus","shop","shopping","store","storefront","symbol"]},{"name":"gps_not_fixed","tags":["destination","direction","disabled","enabled","gps","location","maps","not fixed","off","on","online","place","pointer","slash","tracking"]},{"name":"high_quality","tags":["alphabet","character","definition","display","font","high","hq","letter","movie","movies","quality","resolution","screen","symbol","text","tv","type"]},{"name":"switch_right","tags":["arrows","directional","navigation","right","switch","toggle"]},{"name":"pages","tags":["article","gplus","pages","paper","post","star"]},{"name":"table_restaurant","tags":["bar","dining","table"]},{"name":"speaker_notes_off","tags":["bubble","chat","comment","communicate","disabled","enabled","format","list","message","notes","off","on","slash","speaker","speech","text"]},{"name":"phone_disabled","tags":["call","cell","contact","device","disabled","enabled","hardware","mobile","off","offline","on","phone","slash","telephone"]},{"name":"eject","tags":["disc","drive","dvd","eject","remove","triangle","usb"]},{"name":"control_point_duplicate","tags":["+","add","circle","control","duplicate","multiple","new","plus","point","symbol"]},{"name":"filter","tags":["edit","editing","effect","filter","image","landscape","mountain","mountains","photo","photography","picture","settings"]},{"name":"pest_control","tags":["bug","control","exterminator","insects","pest"]},{"name":"backpack","tags":["back","backpack","bag","book","bookbag","knapsack","pack","storage","travel"]},{"name":"leak_add","tags":["add","connection","data","leak","link","network","service","signals","synce","wireless"]},{"name":"zoom_in_map","tags":["arrow","arrows","destination","in","location","maps","move","place","stop","zoom"]},{"name":"brightness_7","tags":["7","brightness","circle","control","crescent","level","moon","screen","sun"]},{"name":"system_security_update_good","tags":["Android","OS","approve","cell","check","complete","device","done","good","hardware","iOS","mark","mobile","ok","phone","security","select","system","tablet","tick","update","validate","verified","yes"]},{"name":"ring_volume","tags":["call","calling","cell","contact","device","hardware","incoming","mobile","phone","ring","ringer","sound","telephone","volume"]},{"name":"money_off_csred","tags":["bill","card","cart","cash","coin","commerce","credit","csred","currency","disabled","dollars","enabled","money","off","on","online","pay","payment","shopping","slash","symbol"]},{"name":"sports_football","tags":["athlete","athletic","ball","entertainment","exercise","football","game","hobby","social","sports"]},{"name":"nature","tags":["forest","nature","outdoor","outside","park","tree","wilderness"]},{"name":"vibration","tags":["Android","OS","alert","cell","device","hardware","iOS","mobile","mode","motion","notification","phone","silence","silent","tablet","vibrate","vibration"]},{"name":"snippet_folder","tags":["data","doc","document","drive","file","folder","sheet","slide","snippet","storage"]},{"name":"edit_road","tags":["destination","direction","edit","highway","maps","pen","pencil","road","street","traffic"]},{"name":"run_circle","tags":["body","circle","exercise","human","people","person","run","running"]},{"name":"dry_cleaning","tags":["cleaning","dry","hanger","hotel","laundry","places","service","towel"]},{"name":"alarm_off","tags":["alarm","alert","bell","clock","disabled","duration","enabled","notification","off","on","slash","time","timer","watch"]},{"name":"perm_data_setting","tags":["data","gear","info","information","perm","settings"]},{"name":"bedroom_parent","tags":["bed","bedroom","double","full","furniture","home","hotel","house","king","night","parent","pillows","queen","rest","room","sizem master","sleep"]},{"name":"airline_seat_recline_normal","tags":["airline","body","extra","feet","human","leg","legroom","normal","people","person","recline","seat","sitting","space","travel"]},{"name":"currency_bitcoin","tags":["bill","blockchain","card","cash","coin","commerce","cost","credit","currency","digital","dollars","finance","franc","money","online","pay","payment","price","shopping","symbol"]},{"name":"do_disturb_alt","tags":["cancel","close","denied","deny","disturb","do","remove","silence","stop"]},{"name":"sensor_window","tags":["alarm","security","security system"]},{"name":"incomplete_circle","tags":["chart","circle","incomplete"]},{"name":"settings_input_hdmi","tags":["cable","connection","connectivity","definition","hdmi","high","input","plug","plugin","points","settings","video","wire"]},{"name":"camera_indoor","tags":["architecture","building","camera","estate","film","filming","home","house","image","indoor","inside","motion","nest","picture","place","real","residence","residential","shelter","video","videography"]},{"name":"edit_location_alt","tags":["alt","edit","location","pen","pencil","pin"]},{"name":"texture","tags":["diagonal","lines","pattern","stripes","texture"]},{"name":"location_off","tags":["destination","direction","location","maps","off","pin","place","room","stop"]},{"name":"edit_attributes","tags":["approve","attribution","check","complete","done","edit","mark","ok","select","tick","validate","verified","yes"]},{"name":"duo","tags":["call","chat","conference","device","duo","video"]},{"name":"slow_motion_video","tags":["arrow","control","controls","motion","music","play","slow","speed","video"]},{"name":"perm_scan_wifi","tags":["alert","announcement","connection","info","information","internet","network","perm","scan","service","signal","wifi","wireless"]},{"name":"phonelink_setup","tags":["Android","OS","call","chat","device","hardware","iOS","info","mobile","phone","phonelink","settings","setup","tablet","text"]},{"name":"hourglass_disabled","tags":["clock","countdown","disabled","empty","enabled","hourglass","loading","minute","minutes","off","on","slash","time","wait","waiting"]},{"name":"add_to_queue","tags":["+","Android","OS","add","chrome","desktop","device","display","hardware","iOS","mac","monitor","new","plus","queue","screen","symbol","to","web","window"]},{"name":"pie_chart_outline","tags":["analytics","bar","bars","chart","data","diagram","graph","infographic","measure","metrics","outline","pie","statistics","tracking"]},{"name":"playlist_remove","tags":["-","collection","list","minus","music","playlist","remove"]},{"name":"next_week","tags":["arrow","bag","baggage","briefcase","business","case","next","suitcase","week"]},{"name":"church","tags":["christian","christianity","religion","spiritual","worship"]},{"name":"medical_information","tags":["badge","card","health","id","information","medical","services"]},{"name":"view_compact","tags":["compact","grid","layout","pattern","squares","view"]},{"name":"timer_off","tags":["alarm","alert","bell","clock","disabled","duration","enabled","notification","off","on","slash","stop","time","timer","watch"]},{"name":"bluetooth_connected","tags":["bluetooth","cast","connect","connection","device","paring","streaming","symbol","wireless"]},{"name":"photo_size_select_actual","tags":["actual","image","mountain","mountains","photo","photography","picture","select","size"]},{"name":"short_text","tags":["brief","comment","doc","document","note","short","text","write","writing"]},{"name":"bedroom_baby","tags":["babies","baby","bedroom","child","children","home","horse","house","infant","kid","newborn","rocking","room","toddler","young"]},{"name":"video_camera_back","tags":["back","camera","image","landscape","mountain","mountains","photo","photography","picture","rear","video"]},{"name":"bathroom","tags":["bath","bathroom","closet","home","house","place","plumbing","room","shower","sprinkler","wash","water","wc"]},{"name":"downhill_skiing","tags":["athlete","athletic","body","downhill","entertainment","exercise","hobby","human","people","person","ski social","skiing","snow","sports","travel","winter"]},{"name":"filter_list_off","tags":["alt","disabled","edit","filter","list","off","offline","options","refine","sift","slash"]},{"name":"connected_tv","tags":["Android","OS","airplay","chrome","connect","connected","desktop","device","display","hardware","iOS","mac","monitor","screen","screencast","streaming","television","tv","web","window","wireless"]},{"name":"format_indent_increase","tags":["align","alignment","doc","edit","editing","editor","format","increase","indent","indentation","paragraph","sheet","spreadsheet","text","type","writing"]},{"name":"settings_cell","tags":["Android","OS","cell","device","hardware","iOS","mobile","phone","settings","tablet"]},{"name":"remember_me","tags":["Android","OS","avatar","device","hardware","human","iOS","identity","me","mobile","people","person","phone","profile","remember","tablet","user"]},{"name":"kayaking","tags":["athlete","athletic","body","canoe","entertainment","exercise","hobby","human","kayak","kayaking","lake","paddle","paddling","people","person","rafting","river","row","social","sports","summer","travel","water"]},{"name":"switch_access_shortcut_add","tags":["+","access","add","arrow","arrows","direction","navigation","new","north","plus","shortcut","switch","symbol","up"]},{"name":"app_blocking","tags":["Android","OS","app","application","block","blocking","cancel","cell","device","hardware","iOS","mobile","phone","stop","stopped","tablet"]},{"name":"elevator","tags":["body","down","elevator","human","people","person","up"]},{"name":"work_off","tags":["bag","baggage","briefcase","business","case","disabled","enabled","job","off","on","slash","suitcase","work"]},{"name":"sensors_off","tags":["connection","disabled","enabled","network","off","on","scan","sensors","signal","slash","wireless"]},{"name":"stay_primary_portrait","tags":["Android","OS","current","device","hardware","iOS","mobile","phone","portrait","primary","stay","tablet"]},{"name":"cell_tower","tags":["broadcast","casting","cell","network","signal","tower","transmitting","wireless"]},{"name":"moped","tags":["automobile","bike","car","cars","maps","scooter","transportation","vehicle","vespa"]},{"name":"wrong_location","tags":["cancel","close","destination","direction","exit","location","maps","no","pin","place","quit","remove","stop","wrong","x"]},{"name":"groups_2","tags":["body","club","collaboration","crowd","gathering","groups","hair","human","meeting","people","person","social","teams"]},{"name":"public_off","tags":["disabled","earth","enabled","global","globe","map","network","off","on","planet","public","slash","social","space","web","world"]},{"name":"picture_in_picture_alt","tags":["crop","cropped","overlap","photo","picture","position","shape"]},{"name":"chair_alt","tags":["cahir","furniture","home","house","kitchen","lounging","seating","table"]},{"name":"car_repair","tags":["automobile","car","cars","maps","repair","transportation","vehicle"]},{"name":"airplay","tags":["airplay","arrow","connect","control","desktop","device","display","monitor","screen","signal"]},{"name":"nfc","tags":["communication","data","field","mobile","near","nfc","wireless"]},{"name":"line_style","tags":["dash","dotted","line","rule","spacing","style"]},{"name":"transform","tags":["adjust","crop","edit","editing","image","photo","picture","transform"]},{"name":"single_bed","tags":["bed","bedroom","double","furniture","home","hotel","house","king","night","pillows","queen","rest","room","single","sleep","twin"]},{"name":"pattern","tags":["key","login","password","pattern","pin","security","star","unlock"]},{"name":"local_movies","tags":[]},{"name":"repeat_one","tags":["1","arrow","arrows","control","controls","digit","media","music","number","one","repeat","symbol","video"]},{"name":"swap_calls","tags":["arrow","arrows","calls","device","direction","mobile","share","swap"]},{"name":"do_not_disturb_alt","tags":["cancel","close","denied","deny","disturb","do","remove","silence","stop"]},{"name":"smoking_rooms","tags":["allowed","cigarette","places","rooms","smoke","smoking","tobacco","zone"]},{"name":"remove_moderator","tags":["certified","disabled","enabled","moderator","off","on","privacy","private","protect","protection","remove","security","shield","slash","verified"]},{"name":"perm_device_information","tags":["Android","OS","alert","announcement","device","hardware","i","iOS","info","information","mobile","perm","phone","tablet"]},{"name":"wash","tags":["bathroom","clean","fingers","gesture","hand","wash","wc"]},{"name":"mode_standby","tags":["disturb","mode","power","sleep","standby","target"]},{"name":"door_sliding","tags":["auto","automatic","door","doorway","double","entrance","exit","glass","home","house","sliding","two"]},{"name":"skateboarding","tags":["athlete","athletic","body","entertainment","exercise","hobby","human","people","person","skate","skateboarder","skateboarding","social","sports"]},{"name":"difference","tags":["compare","content","copy","cut","diff","difference","doc","document","duplicate","file","multiple","past"]},{"name":"group_remove","tags":["accounts","committee","face","family","friends","group","humans","network","people","persons","profiles","remove","social","team","users"]},{"name":"brightness_high","tags":["auto","brightness","control","high","mobile","monitor","phone","sun"]},{"name":"cabin","tags":["architecture","cabin","camping","cottage","estate","home","house","log","maps","place","real","residence","residential","stay","traveling","wood"]},{"name":"camera_outdoor","tags":["architecture","building","camera","estate","film","filming","home","house","image","motion","nest","outdoor","outside","picture","place","real","residence","residential","shelter","video","videography"]},{"name":"troubleshoot","tags":["analytics","chart","data","diagram","find","glass","graph","infographic","line","look","magnify","magnifying","measure","metrics","search","see","statistics","tracking","troubleshoot"]},{"name":"tablet_android","tags":["OS","android","device","hardware","iOS","ipad","mobile","tablet","web"]},{"name":"house_siding","tags":["architecture","building","construction","estate","exterior","facade","home","house","real","residential","siding"]},{"name":"satellite","tags":["bluetooth","connect","connection","connectivity","data","device","image","internet","landscape","location","maps","mountain","mountains","network","photo","photography","picture","satellite","scan","service","signal","symbol","wireless-- wifi"]},{"name":"motion_photos_on","tags":["animation","circle","disabled","enabled","motion","off","on","photos","play","slash","video"]},{"name":"door_back","tags":["back","closed","door","doorway","entrance","exit","home","house","way"]},{"name":"strikethrough_s","tags":["alphabet","character","cross","doc","edit","editing","editor","font","letter","out","s","sheet","spreadsheet","strikethrough","styles","symbol","text","type","writing"]},{"name":"co2","tags":["carbon","chemical","co2","dioxide","gas"]},{"name":"notifications_paused","tags":["active","alarm","alert","bell","chime","ignore","notifications","notify","paused","quiet","reminder","ring --- pause","sleep","snooze","sound","z","zzz"]},{"name":"currency_yen","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","money","online","pay","payment","price","shopping","symbol","yen"]},{"name":"call_to_action","tags":["action","alert","bar","call","components","cta","design","info","information","interface","layout","message","notification","screen","site","to","ui","ux","web","website","window"]},{"name":"photo_camera_front","tags":["account","camera","face","front","human","image","people","person","photo","photography","picture","portrait","profile","user"]},{"name":"directions_boat_filled","tags":["automobile","boat","car","cars","direction","directions","ferry","filled","maps","public","transportation","vehicle"]},{"name":"subtitles_off","tags":["accessibility","accessible","caption","cc","closed","disabled","enabled","language","off","on","slash","subtitle","subtitles","translate","video"]},{"name":"rotate_90_degrees_ccw","tags":["90","arrow","arrows","ccw","degrees","direction","edit","editing","image","photo","rotate","turn"]},{"name":"vertical_align_center","tags":["align","alignment","arrow","center","doc","down","edit","editing","editor","sheet","spreadsheet","text","type","up","vertical","writing"]},{"name":"living","tags":["chair","comfort","couch","decoration","furniture","home","house","living","lounging","loveseat","room","seat","seating","sofa"]},{"name":"battery_saver","tags":["+","add","battery","charge","charging","new","plus","power","saver","symbol"]},{"name":"hot_tub","tags":["bath","bathing","bathroom","bathtub","hot","hotel","human","jacuzzi","person","shower","spa","steam","travel","tub","water"]},{"name":"play_lesson","tags":["audio","book","bookmark","digital","ebook","lesson","multimedia","play","play lesson","read","reading","ribbon"]},{"name":"update_disabled","tags":["arrow","back","backwards","clock","date","disabled","enabled","forward","history","load","off","on","refresh","reverse","rotate","schedule","slash","time","update"]},{"name":"psychology_alt","tags":["?","assistance","behavior","body","brain","cognitive","function","gear","head","help","human","info","information","intellectual","mental","mind","people","person","preferences","psychiatric","psychology","punctuation","question mark","science","settings","social","support","symbol","therapy","thinking","thoughts"]},{"name":"cast_connected","tags":["Android","OS","airplay","cast","chrome","connect","connected","desktop","device","display","hardware","iOS","mac","monitor","screen","screencast","streaming","television","tv","web","window","wireless"]},{"name":"format_color_reset","tags":["clear","color","disabled","doc","droplet","edit","editing","editor","enabled","fill","format","off","on","paint","reset","sheet","slash","spreadsheet","style","text","type","water","writing"]},{"name":"snooze","tags":["alarm","bell","clock","duration","notification","snooze","time","timer","watch","z"]},{"name":"person_remove_alt_1","tags":[]},{"name":"align_horizontal_left","tags":["align","alignment","format","horizontal","layout","left","lines","paragraph","rule","rules","style","text"]},{"name":"boy","tags":["body","boy","gender","human","male","man","people","person","social","symbol"]},{"name":"battery_5_bar","tags":["5","bar","battery","cell","charge","mobile","power"]},{"name":"mic_external_on","tags":["audio","disabled","enabled","external","mic","microphone","off","on","slash","sound","voice"]},{"name":"voicemail","tags":["call","device","message","missed","mobile","phone","recording","voice","voicemail"]},{"name":"join_full","tags":["circle","combine","command","full","join","left","outer","overlap","right","sql"]},{"name":"looks_5","tags":["5","digit","looks","numbers","square","symbol"]},{"name":"countertops","tags":["counter","countertops","home","house","kitchen","sink","table","tops"]},{"name":"energy_savings_leaf","tags":["eco","energy","leaf","leaves","nest","savings","usage"]},{"name":"safety_divider","tags":["apart","distance","divider","safety","separate","social","space"]},{"name":"move_up","tags":["arrow","direction","jump","move","navigation","transfer","up"]},{"name":"storm","tags":["forecast","hurricane","storm","temperature","twister","weather","wind"]},{"name":"sync_disabled","tags":["360","around","arrow","arrows","direction","disabled","enabled","inprogress","load","loading refresh","off","on","renew","rotate","slash","sync","turn"]},{"name":"javascript","tags":["alphabet","brackets","character","code","css","develop","developer","engineer","engineering","font","html","javascript","letter","platform","symbol","text","type"]},{"name":"tram","tags":["automobile","car","cars","direction","maps","public","rail","subway","train","tram","transportation","vehicle"]},{"name":"app_shortcut","tags":["app","bookmarked","favorite","highlight","important","marked","mobile","save","saved","shortcut","software","special","star"]},{"name":"data_saver_off","tags":["analytics","bar","bars","chart","data","diagram","donut","graph","infographic","measure","metrics","off","on","ring","saver","statistics","tracking"]},{"name":"laptop_windows","tags":["Android","OS","chrome","device","display","hardware","iOS","laptop","mac","monitor","screen","web","window","windows"]},{"name":"doorbell","tags":["alarm","bell","door","doorbell","home","house","ringing"]},{"name":"hd","tags":["alphabet","character","definition","display","font","hd","high","letter","movie","movies","resolution","screen","symbol","text","tv","type"]},{"name":"file_download_off","tags":["arrow","disabled","down","download","drive","enabled","export","file","install","off","on","save","slash","upload"]},{"name":"apps_outage","tags":["all","applications","apps","circles","collection","components","dots","grid","interface","outage","squares","ui","ux"]},{"name":"taxi_alert","tags":["!","alert","attention","automobile","cab","car","cars","caution","danger","direction","error","exclamation","important","lyft","maps","mark","notification","public","symbol","taxi","transportation","uber","vehicle","warning","yellow"]},{"name":"breakfast_dining","tags":["bakery","bread","breakfast","butter","dining","food","toast"]},{"name":"brightness_medium","tags":["auto","brightness","control","medium","mobile","monitor","phone","sun"]},{"name":"gradient","tags":["color","edit","editing","effect","filter","gradient","image","images","photography","picture","pictures"]},{"name":"swipe_left","tags":["arrow","arrows","finger","hand","hit","left","navigation","reject","strike","swing","swipe","take"]},{"name":"soup_kitchen","tags":["breakfast","brunch","dining","food","kitchen","lunch","meal","soup"]},{"name":"voice_over_off","tags":["account","disabled","enabled","face","human","off","on","over","people","person","profile","recording","slash","speak","speaking","speech","transcript","user","voice"]},{"name":"water_damage","tags":["architecture","building","damage","drop","droplet","estate","house","leak","plumbing","real","residence","residential","shelter","water"]},{"name":"abc","tags":["alphabet","character","font","letter","symbol","text","type"]},{"name":"data_saver_on","tags":["+","add","analytics","chart","data","diagram","graph","infographic","measure","metrics","new","on","plus","ring","saver","statistics","symbol","tracking"]},{"name":"signal_wifi_0_bar","tags":["0","bar","cell","cellular","data","internet","mobile","network","phone","signal","wifi","wireless"]},{"name":"brightness_low","tags":["auto","brightness","control","low","mobile","monitor","phone","sun"]},{"name":"device_unknown","tags":["?","Android","OS","assistance","cell","device","hardware","help","iOS","info","information","mobile","phone","punctuation","question mark","support","symbol","tablet","unknown"]},{"name":"fire_extinguisher","tags":["emergency","extinguisher","fire","water"]},{"name":"fitbit","tags":["athlete","athletic","exercise","fitbit","fitness","hobby","logo"]},{"name":"bedroom_child","tags":["bed","bedroom","child","children","furniture","home","hotel","house","kid","night","pillows","rest","room","size","sleep","twin","young"]},{"name":"closed_caption_off","tags":["accessible","alphabet","caption","cc","character","closed","decoder","font","language","letter","media","movies","off","outline","subtitle","subtitles","symbol","text","tv","type"]},{"name":"bluetooth_searching","tags":["bluetooth","connection","device","paring","search","searching","symbol"]},{"name":"content_paste_off","tags":["clipboard","content","disabled","doc","document","enabled","file","off","on","paste","slash"]},{"name":"hexagon","tags":["hexagon","shape","six sides"]},{"name":"tap_and_play","tags":["Android","OS wifi","cell","connection","device","hardware","iOS","internet","mobile","network","phone","play","signal","tablet","tap","to","wireless"]},{"name":"domain_add","tags":["+","add","apartment","architecture","building","business","domain","estate","home","new","place","plus","real","residence","residential","shelter","symbol","web","www"]},{"name":"signpost","tags":["arrow","direction","left","maps","right","signal","signs","street","traffic"]},{"name":"screenshot","tags":["Android","OS","cell","crop","device","hardware","iOS","mobile","phone","screen","screenshot","tablet"]},{"name":"network_cell","tags":["cell","cellular","data","internet","mobile","network","phone","speed","wifi","wireless"]},{"name":"repeat_on","tags":["arrow","arrows","control","controls","media","music","on","repeat","video"]},{"name":"charging_station","tags":["Android","OS","battery","bolt","cell","charging","device","electric","hardware","iOS","lightning","mobile","phone","station","tablet","thunderbolt"]},{"name":"grid_4x4","tags":["4","by","grid","layout","lines","space"]},{"name":"assistant_photo","tags":["assistant","flag","photo","recommendation","smart","star","suggestion"]},{"name":"carpenter","tags":["building","carpenter","construction","cutting","handyman","repair","saw","tool"]},{"name":"private_connectivity","tags":["connectivity","lock","locked","password","privacy","private","protection","safety","secure","security"]},{"name":"mobiledata_off","tags":["arrow","data","disabled","down","enabled","internet","mobile","network","off","on","slash","speed","up","wifi","wireless"]},{"name":"atm","tags":["alphabet","atm","automated","bill","card","cart","cash","character","coin","commerce","credit","currency","dollars","font","letter","machine","money","online","pay","payment","shopping","symbol","teller","text","type"]},{"name":"rv_hookup","tags":["arrow","attach","automobile","automotive","back","car","cars","connect","direction","hookup","left","maps","public","right","rv","trailer","transportation","travel","truck","van","vehicle"]},{"name":"replay_30","tags":["30","arrow","arrows","control","controls","digit","music","number","refresh","renew","repeat","replay","symbol","thirty","video"]},{"name":"offline_share","tags":["Android","OS","arrow","cell","connect","device","direction","hardware","iOS","link","mobile","multiple","offline","phone","right","share","tablet"]},{"name":"settings_input_svideo","tags":["cable","connection","connectivity","definition","input","plug","plugin","points","settings","standard","svideo","video"]},{"name":"soap","tags":["bathroom","clean","fingers","gesture","hand","soap","wash","wc"]},{"name":"baby_changing_station","tags":["babies","baby","bathroom","body","changing","child","children","father","human","infant","kids","mother","newborn","people","person","station","toddler","wc","young"]},{"name":"sports_cricket","tags":["athlete","athletic","ball","bat","cricket","entertainment","exercise","game","hobby","social","sports"]},{"name":"ad_units","tags":["Android","OS","ad","banner","cell","device","hardware","iOS","mobile","notification","notifications","phone","tablet","top","units"]},{"name":"wb_twilight","tags":["balance","light","lighting","noon","sun","sunset","twilight","wb","white"]},{"name":"no_encryption","tags":["disabled","enabled","encryption","lock","no","off","on","password","safety","security","slash"]},{"name":"table_bar","tags":["bar","cafe","round","table"]},{"name":"diversity_2","tags":["committee","diverse","diversity","family","friends","group","groups","heart","humans","network","people","persons","social","team"]},{"name":"subway","tags":["automobile","bike","car","cars","maps","rail","scooter","subway","train","transportation","travel","tunnel","underground","vehicle","vespa"]},{"name":"browser_updated","tags":["Android","OS","arrow","browser","chrome","desktop","device","display","download","hardware","iOS","mac","monitor","screen","updated","web","window"]},{"name":"currency_pound","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","money","online","pay","payment","pound","price","shopping","symbol"]},{"name":"transit_enterexit","tags":["arrow","direction","enterexit","maps","navigation","route","transit","transportation"]},{"name":"contrast","tags":["black","contrast","edit","editing","effect","filter","grayscale","image","images","photography","picture","pictures","settings","white"]},{"name":"lightbulb_circle","tags":["alert","announcement","idea","info","information","light","lightbulb"]},{"name":"rectangle","tags":["four sides","parallelograms","polygons","quadrilaterals","recangle","shape"]},{"name":"call_merge","tags":["arrow","call","device","merge","mobile"]},{"name":"hide_image","tags":["disabled","enabled","hide","image","landscape","mountain","mountains","off","on","photo","photography","picture","slash"]},{"name":"shield_moon","tags":["certified","do not disturb","moon","night","privacy","private","protect","protection","security","shield","verified"]},{"name":"group_off","tags":["body","club","collaboration","crowd","gathering","group","human","meeting","off","people","person","social","teams"]},{"name":"music_off","tags":["audio","audiotrack","disabled","enabled","key","music","note","off","on","slash","sound","track"]},{"name":"bluetooth_disabled","tags":["bluetooth","cast","connect","connection","device","disabled","enabled","off","offline","on","paring","slash","streaming","symbol","wireless"]},{"name":"flip_to_back","tags":["arrange","arrangement","back","flip","format","front","layout","move","order","sort","to"]},{"name":"sd_card","tags":["camera","card","digital","memory","photos","sd","secure","storage"]},{"name":"exposure_plus_1","tags":["1","add","brightness","contrast","digit","edit","editing","effect","exposure","image","number","photo","photography","plus","settings","symbol"]},{"name":"view_array","tags":["array","design","format","grid","layout","view","website"]},{"name":"sports_mma","tags":["arts","athlete","athletic","boxing","combat","entertainment","exercise","fighting","game","glove","hobby","martial","mixed","mma","social","sports"]},{"name":"straight","tags":["arrow","arrows","direction","directions","maps","navigation","path","route","sign","straight","traffic","up"]},{"name":"thermostat_auto","tags":["A","auto","celsius","fahrenheit","meter","temp","temperature","thermometer","thermostat"]},{"name":"mobile_screen_share","tags":["Android","OS","cast","cell","device","hardware","iOS","mirror","mobile","monitor","phone","screen","screencast","share","stream","streaming","tablet","tv","wireless"]},{"name":"phone_missed","tags":["arrow","call","cell","contact","device","hardware","missed","mobile","phone","telephone"]},{"name":"brunch_dining","tags":["breakfast","brunch","champagne","dining","drink","food","lunch","meal"]},{"name":"featured_video","tags":["advertised","advertisement","featured","highlighted","recommended","video","watch"]},{"name":"merge","tags":["arrow","arrows","direction","directions","maps","merge","navigation","path","route","sign","traffic"]},{"name":"open_in_new_off","tags":["arrow","box","disabled","enabled","export","in","new","off","on","open","slash","window"]},{"name":"hdr_auto","tags":["A","alphabet","auto","camera","character","circle","dynamic","font","hdr","high","letter","photo","range","symbol","text","type"]},{"name":"join_inner","tags":["circle","command","inner","join","matching","overlap","sql","values"]},{"name":"solar_power","tags":["eco","energy","heat","nest","power","solar","sun","sunny"]},{"name":"crop_16_9","tags":["16","9","adjust","adjustments","area","by","crop","edit","editing","frame","image","images","photo","photos","rectangle","settings","size","square"]},{"name":"swipe_right","tags":["accept","arrows","direction","finger","hands","hit","navigation","right","strike","swing","swpie","take"]},{"name":"phonelink_erase","tags":["Android","OS","cancel","cell","close","connection","device","erase","exit","hardware","iOS","mobile","no","phone","phonelink","remove","stop","tablet","x"]},{"name":"smoke_free","tags":["cigarette","disabled","enabled","free","never","no","off","on","places","prohibited","slash","smoke","smoking","tobacco","warning","zone"]},{"name":"install_desktop","tags":["Android","OS","chrome","desktop","device","display","fix","hardware","iOS","install","mac","monitor","place","pwa","screen","web","window"]},{"name":"shutter_speed","tags":["aperture","camera","duration","image","lens","photo","photography","photos","picture","setting","shutter","speed","stop","time","timer","watch"]},{"name":"keyboard_hide","tags":["arrow","computer","device","down","hardware","hide","input","keyboard","keypad","text"]},{"name":"exposure","tags":["add","brightness","contrast","edit","editing","effect","exposure","image","minus","photo","photography","picture","plus","settings","subtract"]},{"name":"nordic_walking","tags":["athlete","athletic","body","entertainment","exercise","hiking","hobby","human","nordic","people","person","social","sports","travel","walker","walking"]},{"name":"umbrella","tags":["beach","protection","rain","sun","sunny","umbrella"]},{"name":"move_down","tags":["arrow","direction","down","jump","move","navigation","transfer"]},{"name":"filter_2","tags":["2","digit","edit","editing","effect","filter","image","images","multiple","number","photography","picture","pictures","settings","stack","symbol"]},{"name":"photo_album","tags":["album","archive","bookmark","image","label","library","mountain","mountains","photo","photography","picture","ribbon","save","tag"]},{"name":"security_update_good","tags":["Android","OS","checkmark","device","good","hardware","iOS","mobile","ok","phone","security","tablet","tick","update"]},{"name":"ssid_chart","tags":["chart","graph","lines","network","ssid","wifi"]},{"name":"score","tags":["2k","alphabet","analytics","bar","bars","character","chart","data","diagram","digit","font","graph","infographic","letter","measure","metrics","number","score","statistics","symbol","text","tracking","type"]},{"name":"swipe_up","tags":["arrows","direction","disable","enable","finger","hands","hit","navigation","strike","swing","swpie","take","up"]},{"name":"battery_4_bar","tags":["4","bar","battery","cell","charge","mobile","power"]},{"name":"all_out","tags":["all","circle","out","shape"]},{"name":"battery_unknown","tags":["?","assistance","battery","cell","charge","help","info","information","mobile","power","punctuation","question mark","support","symbol","unknown"]},{"name":"sports_golf","tags":["athlete","athletic","ball","club","entertainment","exercise","game","golf","golfer","golfing","hobby","social","sports"]},{"name":"sports_martial_arts","tags":["arts","athlete","athletic","entertainment","exercise","hobby","human","karate","martial","people","person","social","sports"]},{"name":"filter_tilt_shift","tags":["blur","center","edit","editing","effect","filter","focus","image","images","photography","picture","pictures","shift","tilt"]},{"name":"electric_bike","tags":["bike","electric","electricity","maps","scooter","transportation","travel","vespa"]},{"name":"border_all","tags":["all","border","doc","edit","editing","editor","sheet","spreadsheet","stroke","text","type","writing"]},{"name":"auto_mode","tags":["ai","around","arrow","arrows","artificial","auto","automatic","automation","custom","direction","genai","inprogress","intelligence","load","loading refresh","magic","mode","navigation","nest","renew","rotate","smart","spark","sparkle","star","turn"]},{"name":"hvac","tags":["air","conditioning","heating","hvac","ventilation"]},{"name":"scanner","tags":["copy","device","hardware","machine","scan","scanner"]},{"name":"shuffle_on","tags":["arrow","arrows","control","controls","music","on","random","shuffle","video"]},{"name":"wifi_calling_3","tags":["3","calling","cell","cellular","data","internet","mobile","network","phone","speed","wifi","wireless"]},{"name":"signal_wifi_off","tags":["cell","cellular","data","disabled","enabled","internet","mobile","network","off","on","phone","signal","slash","speed","wifi","wireless"]},{"name":"girl","tags":["body","female","gender","girl","human","lady","people","person","social","symbol","woman","women"]},{"name":"shop_2","tags":["2","add","arrow","buy","cart","google","play","purchase","shop","shopping"]},{"name":"hdr_strong","tags":["circles","dots","dynamic","enhance","hdr","high","range","strong"]},{"name":"directions_transit","tags":["automobile","car","cars","direction","directions","maps","public","rail","subway","train","transit","transportation","vehicle"]},{"name":"label_off","tags":["disabled","enabled","favorite","indent","label","library","mail","off","on","remember","save","slash","stamp","sticker","tag","wing"]},{"name":"tablet","tags":["Android","OS","device","hardware","iOS","ipad","mobile","tablet","web"]},{"name":"5g","tags":["5g","alphabet","cellular","character","data","digit","font","letter","mobile","network","number","phone","signal","speed","symbol","text","type","wifi"]},{"name":"vrpano","tags":["angle","image","landscape","mountain","mountains","panorama","photo","photography","picture","view","vrpano","wide"]},{"name":"forward_30","tags":["30","arrow","control","controls","digit","fast","forward","music","number","seconds","symbol","video"]},{"name":"battery_0_bar","tags":["0","bar","battery","cell","charge","mobile","power"]},{"name":"airline_seat_recline_extra","tags":["airline","body","extra","feet","human","leg","legroom","people","person","seat","sitting","space","travel"]},{"name":"looks","tags":["circle","half","looks","rainbow"]},{"name":"linked_camera","tags":["camera","connect","connection","lens","linked","network","photo","photography","picture","signal","signals","sync","wireless"]},{"name":"paragliding","tags":["athlete","athletic","body","entertainment","exercise","fly","gliding","hobby","human","parachute","paragliding","people","person","sky","skydiving","social","sports","travel"]},{"name":"electric_scooter","tags":["bike","electric","maps","scooter","transportation","vehicle","vespa"]},{"name":"settings_system_daydream","tags":["backup","cloud","daydream","drive","settings","storage","system"]},{"name":"format_indent_decrease","tags":["align","alignment","decrease","doc","edit","editing","editor","format","indent","indentation","paragraph","sheet","spreadsheet","text","type","writing"]},{"name":"tapas","tags":["appetizer","brunch","dinner","food","lunch","restaurant","snack","tapas"]},{"name":"brightness_3","tags":["3","brightness","circle","control","crescent","level","moon","screen"]},{"name":"tab_unselected","tags":["browser","computer","document","documents","folder","internet","tab","tabs","unselected","web","website","window","windows"]},{"name":"density_small","tags":["density","horizontal","lines","rule","rules","small"]},{"name":"blur_circular","tags":["blur","circle","circular","dots","edit","editing","effect","enhance","filter"]},{"name":"rice_bowl","tags":["bowl","dinner","food","lunch","meal","restaurant","rice"]},{"name":"rounded_corner","tags":["adjust","corner","edit","rounded","shape","square","transform"]},{"name":"person_add_disabled","tags":["+","account","add","disabled","enabled","face","human","new","off","offline","on","people","person","plus","profile","slash","symbol","user"]},{"name":"music_video","tags":["band","music","recording","screen","tv","video","watch"]},{"name":"looks_6","tags":["6","digit","looks","numbers","square","symbol"]},{"name":"do_not_touch","tags":["disabled","do","enabled","fingers","gesture","hand","not","off","on","slash","touch"]},{"name":"playlist_add_circle","tags":["add","album","artist","audio","cd","check","circle","collection","list","mark","music","playlist","record","sound","track"]},{"name":"domain_disabled","tags":["apartment","architecture","building","business","company","disabled","domain","enabled","estate","home","internet","maps","off","office","offline","on","place","real","residence","residential","slash","web","website"]},{"name":"flash_auto","tags":["a","auto","bolt","electric","fast","flash","lightning","thunderbolt"]},{"name":"6_ft_apart","tags":["6","apart","body","covid","distance","feet","ft","human","people","person","social"]},{"name":"signal_wifi_bad","tags":["bad","bar","cancel","cell","cellular","close","data","exit","internet","mobile","network","no","phone","quit","remove","signal","stop","wifi","wireless","x"]},{"name":"crisis_alert","tags":["!","alert","attention","bullseye","caution","crisis","danger","error","exclamation","important","mark","notification","symbol","target","warning"]},{"name":"queue_play_next","tags":["+","add","arrow","desktop","device","display","hardware","monitor","new","next","play","plus","queue","screen","steam","symbol","tv"]},{"name":"format_clear","tags":["T","alphabet","character","clear","disabled","doc","edit","editing","editor","enabled","font","format","letter","off","on","sheet","slash","spreadsheet","style","symbol","text","type","writing"]},{"name":"bus_alert","tags":["!","alert","attention","automobile","bus","car","cars","caution","danger","error","exclamation","important","maps","mark","notification","symbol","transportation","vehicle","warning"]},{"name":"party_mode","tags":["camera","lens","mode","party","photo","photography","picture"]},{"name":"snowboarding","tags":["athlete","athletic","body","entertainment","exercise","hobby","human","people","person","snow","snowboarding","social","sports","travel","winter"]},{"name":"text_rotate_vertical","tags":["A","alphabet","arrow","character","down","field","font","letter","move","rotate","symbol","text","type","vertical"]},{"name":"motion_photos_auto","tags":["A","alphabet","animation","auto","automatic","character","circle","font","gif","letter","live","motion","photos","symbol","text","type","video"]},{"name":"crop_portrait","tags":["adjust","adjustments","area","crop","edit","editing","frame","image","images","photo","photos","portrait","rectangle","settings","size","square"]},{"name":"thunderstorm","tags":["bolt","climate","cloud","cloudy","lightning","rain","rainfall","rainstorm","storm","thunder","thunderstorm","weather"]},{"name":"battery_6_bar","tags":["6","bar","battery","cell","charge","mobile","power"]},{"name":"space_bar","tags":["bar","keyboard","line","space"]},{"name":"replay_5","tags":["5","arrow","arrows","control","controls","digit","five","music","number","refresh","renew","repeat","replay","symbol","video"]},{"name":"local_car_wash","tags":["automobile","car","cars","local","maps","transportation","travel","vehicle","wash"]},{"name":"folder_delete","tags":["bin","can","data","delete","doc","document","drive","file","folder","folders","garbage","remove","sheet","slide","storage","trash"]},{"name":"data_thresholding","tags":["data","hidden","privacy","thresholding","thresold"]},{"name":"connecting_airports","tags":["airplane","airplanes","airport","airports","connecting","flight","plane","transportation","travel","trip"]},{"name":"access_alarms","tags":[]},{"name":"tty","tags":["call","cell","contact","deaf","device","hardware","impaired","mobile","phone","speech","talk","telephone","text","tty"]},{"name":"audio_file","tags":["audio","doc","document","key","music","note","sound","track"]},{"name":"egg","tags":["breakfast","brunch","egg","food"]},{"name":"balcony","tags":["architecture","balcony","doors","estate","home","house","maps","out","outside","place","real","residence","residential","stay","terrace","window"]},{"name":"kitesurfing","tags":["athlete","athletic","beach","body","entertainment","exercise","hobby","human","kitesurfing","people","person","social","sports","surf","travel","water"]},{"name":"call_missed_outgoing","tags":["arrow","call","device","missed","mobile","outgoing"]},{"name":"local_hotel","tags":["body","hotel","human","local","people","person","sleep","stay","travel","trip"]},{"name":"text_increase","tags":["+","add","alphabet","character","font","increase","letter","new","plus","resize","symbol","text","type"]},{"name":"speaker_phone","tags":["Android","OS","cell","device","hardware","iOS","mobile","phone","sound","speaker","tablet","volume"]},{"name":"no_food","tags":["disabled","drink","enabled","fastfood","food","hamburger","meal","no","off","on","slash"]},{"name":"brightness_2","tags":["2","brightness","circle","control","crescent","level","moon","screen"]},{"name":"mode_of_travel","tags":["arrow","destination","direction","location","maps","mode","of","pin","place","stop","transportation","travel","trip"]},{"name":"format_line_spacing","tags":["align","alignment","doc","edit","editing","editor","format","line","sheet","spacing","spreadsheet","text","type","writing"]},{"name":"iso","tags":["add","edit","editing","effect","image","iso","minus","photography","picture","plus","sensor","shutter","speed","subtract"]},{"name":"explore_off","tags":["compass","destination","direction","disabled","east","enabled","explore","location","maps","needle","north","off","on","slash","south","travel","west"]},{"name":"drive_file_move_rtl","tags":["arrow","arrows","data","direction","doc","document","drive","file","folder","folders","left","move","rtl","sheet","side","slide","storage"]},{"name":"cell_wifi","tags":["cell","connection","data","internet","mobile","network","phone","service","signal","wifi","wireless"]},{"name":"tonality","tags":["circle","edit","editing","filter","image","photography","picture","tonality"]},{"name":"spoke","tags":["connection","network","radius","spoke"]},{"name":"photo_filter","tags":["ai","artificial","automatic","automation","custom","filter","filters","genai","image","intelligence","magic","photo","photography","picture","smart","spark","sparkle","star"]},{"name":"desktop_access_disabled","tags":["Android","OS","access","chrome","desktop","device","disabled","display","enabled","hardware","iOS","mac","monitor","off","offline","on","screen","slash","web","window"]},{"name":"sports_gymnastics","tags":["athlete","athletic","entertainment","exercise","gymnastics","hobby","social","sports"]},{"name":"houseboat","tags":["architecture","beach","boat","estate","floating","home","house","houseboat","maps","place","real","residence","residential","sea","stay","traveling","vacation"]},{"name":"fence","tags":["backyard","barrier","boundaries","boundary","fence","home","house","protection","yard"]},{"name":"commit","tags":["accomplish","bind","circle","commit","dedicate","execute","line","perform","pledge"]},{"name":"photo_size_select_small","tags":["adjust","album","edit","editing","image","large","library","mountain","mountains","photo","photography","picture","select","size","small"]},{"name":"signal_wifi_connected_no_internet_4","tags":["4","cell","cellular","connected","data","internet","mobile","network","no","offline","phone","signal","wifi","wireless","x"]},{"name":"horizontal_distribute","tags":["alignment","distribute","format","horizontal","layout","lines","paragraph","rule","rules","style","text"]},{"name":"report_off","tags":["!","alert","attention","caution","danger","disabled","enabled","error","exclamation","important","mark","notification","octagon","off","offline","on","report","slash","symbol","warning"]},{"name":"polyline","tags":["compose","create","design","draw","line","polyline","vector"]},{"name":"art_track","tags":["album","art","artist","audio","image","music","photo","photography","picture","sound","track","tracks"]},{"name":"crop_7_5","tags":["5","7","adjust","adjustments","area","by","crop","editing","frame","image","images","photo","photos","rectangle","settings","size","square"]},{"name":"filter_hdr","tags":["camera","edit","editing","effect","filter","hdr","image","mountain","mountains","photo","photography","picture"]},{"name":"text_rotation_none","tags":["A","alphabet","arrow","character","field","font","letter","move","none","rotate","symbol","text","type"]},{"name":"battery_3_bar","tags":["3","bar","battery","cell","charge","mobile","power"]},{"name":"align_vertical_bottom","tags":["align","alignment","bottom","format","layout","lines","paragraph","rule","rules","style","text","vertical"]},{"name":"stop_screen_share","tags":["Android","OS","arrow","cast","chrome","device","disabled","display","enabled","hardware","iOS","laptop","mac","mirror","monitor","off","offline","on","screen","share","slash","steam","stop","streaming","web","window"]},{"name":"imagesearch_roller","tags":["art","image","imagesearch","paint","roller","search"]},{"name":"bento","tags":["bento","box","dinner","food","lunch","meal","restaurant","takeout"]},{"name":"rotate_90_degrees_cw","tags":["90","arrow","arrows","ccw","degrees","direction","edit","editing","image","photo","rotate","turn"]},{"name":"install_mobile","tags":["Android","OS","cell","device","hardware","iOS","install","mobile","phone","pwa","tablet"]},{"name":"hearing_disabled","tags":["accessibility","accessible","aid","disabled","ear","enabled","handicap","hearing","help","impaired","listen","off","on","slash","sound","volume"]},{"name":"video_file","tags":["camera","doc","document","film","filming","hardware","image","motion","picture","video","videography"]},{"name":"mms","tags":["bubble","chat","comment","communicate","feedback","image","landscape","message","mms","mountain","mountains","multimedia","photo","photography","picture","speech"]},{"name":"crop_rotate","tags":["adjust","adjustments","area","arrow","arrows","crop","edit","editing","frame","image","images","photo","photos","rotate","settings","size","turn"]},{"name":"wheelchair_pickup","tags":["accessibility","accessible","body","handicap","help","human","person","pickup","wheelchair"]},{"name":"aod","tags":["Android","OS","always","aod","device","display","hardware","homescreen","iOS","mobile","on","phone","tablet"]},{"name":"castle","tags":["castle","fort","fortress","mansion","palace"]},{"name":"interpreter_mode","tags":["interpreter","language","microphone","mode","person","speaking","symbol"]},{"name":"access_alarm","tags":[]},{"name":"forward_5","tags":["10","5","arrow","control","controls","digit","fast","forward","music","number","seconds","symbol","video"]},{"name":"add_to_home_screen","tags":["Android","OS","add to","arrow","cell","device","hardware","home","iOS","mobile","phone","screen","tablet","up"]},{"name":"not_accessible","tags":["accessibility","accessible","body","handicap","help","human","not","person","wheelchair"]},{"name":"signal_cellular_0_bar","tags":["0","bar","cell","cellular","data","internet","mobile","network","phone","signal","speed","wifi","wireless"]},{"name":"stadium","tags":["activity","amphitheater","arena","coliseum","event","local","stadium","star","things","ticket"]},{"name":"photo_size_select_large","tags":["adjust","album","edit","editing","image","large","library","mountain","mountains","photo","photography","picture","select","size"]},{"name":"groups_3","tags":["abstract","body","club","collaboration","crowd","gathering","groups","human","meeting","people","person","social","teams"]},{"name":"snowshoeing","tags":["body","human","people","person","snow","snowshoe","snowshoeing","sports","travel","walking","winter"]},{"name":"view_kanban","tags":["grid","kanban","layout","pattern","squares","view"]},{"name":"candlestick_chart","tags":["analytics","candlestick","chart","data","diagram","finance","graph","infographic","measure","metrics","statistics","tracking"]},{"name":"filter_3","tags":["3","digit","edit","editing","effect","filter","image","images","multiple","number","photography","picture","pictures","settings","stack","symbol"]},{"name":"arrow_outward","tags":["app","application","arrow","arrows","components","direction","forward","interface","navigation","right","screen","site","ui","ux","web","website"]},{"name":"align_horizontal_center","tags":["align","alignment","center","format","horizontal","layout","lines","paragraph","rule","rules","style","text"]},{"name":"flashlight_off","tags":["disabled","enabled","flash","flashlight","light","off","on","slash"]},{"name":"security_update","tags":["Android","OS","arrow","device","down","download","hardware","iOS","mobile","phone","security","tablet","update"]},{"name":"iron","tags":["appliance","clothes","electric","iron","ironing","machine","object"]},{"name":"print_disabled","tags":["disabled","enabled","off","on","paper","print","printer","slash"]},{"name":"pin_invoke","tags":["action","arrow","dot","invoke","pin"]},{"name":"speaker_group","tags":["box","electronic","group","loud","multiple","music","sound","speaker","stereo","system","video"]},{"name":"exposure_zero","tags":["0","brightness","contrast","digit","edit","editing","effect","exposure","image","number","photo","photography","settings","symbol","zero"]},{"name":"bungalow","tags":["architecture","bungalow","cottage","estate","home","house","maps","place","real","residence","residential","stay","traveling"]},{"name":"streetview","tags":["maps","street","streetview","view"]},{"name":"swipe_down","tags":["arrows","direction","disable","down","enable","finger","hands","hit","navigation","strike","swing","swpie","take"]},{"name":"hdr_weak","tags":["circles","dots","dynamic","enhance","hdr","high","range","weak"]},{"name":"css","tags":["alphabet","brackets","character","code","css","develop","developer","engineer","engineering","font","html","letter","platform","symbol","text","type"]},{"name":"call_missed","tags":["arrow","call","device","missed","mobile"]},{"name":"gps_off","tags":["destination","direction","disabled","enabled","gps","location","maps","not fixed","off","offline","on","place","pointer","slash","tracking"]},{"name":"sports_hockey","tags":["athlete","athletic","entertainment","exercise","game","hobby","hockey","social","sports","sticks"]},{"name":"ice_skating","tags":["athlete","athletic","entertainment","exercise","hobby","ice","shoe","skates","skating","social","sports","travel"]},{"name":"keyboard_capslock","tags":["arrow","capslock","keyboard","up"]},{"name":"earbuds","tags":["accessory","audio","earbuds","earphone","headphone","listen","music","sound"]},{"name":"camera_front","tags":["body","camera","front","human","lens","mobile","person","phone","photography","portrait","selfie"]},{"name":"vertical_distribute","tags":["alignment","distribute","format","layout","lines","paragraph","rule","rules","style","text","vertical"]},{"name":"currency_ruble","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","money","online","pay","payment","price","ruble","shopping","symbol"]},{"name":"signal_wifi_statusbar_null","tags":["cell","cellular","data","internet","mobile","network","null","phone","signal","speed","statusbar","wifi","wireless"]},{"name":"align_horizontal_right","tags":["align","alignment","format","horizontal","layout","lines","paragraph","right","rule","rules","style","text"]},{"name":"crop_5_4","tags":["4","5","adjust","adjustments","area","by","crop","edit","editing settings","frame","image","images","photo","photos","rectangle","size","square"]},{"name":"format_strikethrough","tags":["alphabet","character","doc","edit","editing","editor","font","format","letter","sheet","spreadsheet","strikethrough","style","symbol","text","type","writing"]},{"name":"face_6","tags":["account","emoji","eyes","face","human","lock","log","login","logout","people","person","profile","recognition","security","social","thumbnail","unlock","user"]},{"name":"join_left","tags":["circle","command","join","left","matching","overlap","sql","values"]},{"name":"explicit","tags":["adult","alphabet","character","content","e","explicit","font","language","letter","media","movies","music","symbol","text","type"]},{"name":"extension_off","tags":["disabled","enabled","extended","extension","jigsaw","off","on","piece","puzzle","shape","slash"]},{"name":"perm_camera_mic","tags":["camera","image","microphone","min","perm","photo","photography","picture","speaker"]},{"name":"sports_rugby","tags":["athlete","athletic","ball","entertainment","exercise","game","hobby","rugby","social","sports"]},{"name":"pause_presentation","tags":["app","application desktop","device","pause","present","presentation","screen","share","site","slides","web","website","window","www"]},{"name":"south_america","tags":["continent","landscape","place","region","south america"]},{"name":"sd_storage","tags":["camera","card","data","digital","memory","sd","secure","storage"]},{"name":"superscript","tags":["2","doc","edit","editing","editor","gmail","novitas","sheet","spreadsheet","style","superscript","symbol","text","writing","x"]},{"name":"4g_mobiledata","tags":["4g","alphabet","cellular","character","digit","font","letter","mobile","mobiledata","network","number","phone","signal","speed","symbol","text","type","wifi"]},{"name":"pinch","tags":["arrow","arrows","compress","direction","finger","grasp","hand","navigation","nip","pinch","squeeze","tweak"]},{"name":"lock_person","tags":[]},{"name":"grid_3x3","tags":["3","grid","layout","line","space"]},{"name":"mark_unread_chat_alt","tags":["bubble","chat","circle","comment","communicate","mark","message","notification","speech","unread"]},{"name":"web_stories","tags":["google","images","logo","stories","web"]},{"name":"safety_check","tags":["certified","check","clock","privacy","private","protect","protection","safety","schedule","security","shield","time","verified"]},{"name":"filter_frames","tags":["boarders","border","camera","center","edit","editing","effect","filter","filters","focus","frame","frames","image","options","photo","photography","picture"]},{"name":"spatial_audio_off","tags":["audio","disabled","enabled","music","note","off","offline","on","slash","sound","spatial"]},{"name":"directions_subway","tags":["automobile","car","cars","direction","directions","maps","public","rail","subway","train","transportation","vehicle"]},{"name":"reset_tv","tags":["arrow","device","hardware","monitor","reset","television","tv"]},{"name":"4k","tags":["4000","4K","alphabet","character","digit","display","font","letter","number","pixel","pixels","resolution","symbol","text","type","video"]},{"name":"burst_mode","tags":["burst","image","landscape","mode","mountain","mountains","multiple","photo","photography","picture"]},{"name":"chalet","tags":["architecture","chalet","cottage","estate","home","house","maps","place","real","residence","residential","stay","traveling"]},{"name":"battery_1_bar","tags":["1","bar","battery","cell","charge","mobile","power"]},{"name":"elderly_woman","tags":["body","cane","elderly","female","gender","girl","human","lady","old","people","person","senior","social","symbol","woman","women"]},{"name":"headset_off","tags":["accessory","audio","chat","device","disabled","ear","earphone","enabled","headphones","headset","listen","mic","music","off","on","slash","sound","talk"]},{"name":"swipe_vertical","tags":["arrows","direction","finger","hands","hit","navigation","strike","swing","swpie","take","verticle"]},{"name":"crib","tags":["babies","baby","bassinet","bed","child","children","cradle","crib","infant","kid","newborn","sleeping","toddler"]},{"name":"video_label","tags":["label","screen","video","window"]},{"name":"fiber_smart_record","tags":["circle","dot","fiber","play","record","smart","watch"]},{"name":"brightness_auto","tags":["A","auto","brightness","control","display","level","mobile","monitor","phone","screen","sun"]},{"name":"margin","tags":["design","layout","margin","padding","size","square"]},{"name":"punch_clock","tags":["clock","date","punch","schedule","time","timer","timesheet"]},{"name":"compass_calibration","tags":["calibration","compass","connection","internet","location","maps","network","refresh","service","signal","wifi","wireless"]},{"name":"mosque","tags":["islam","islamic","masjid","muslim","religion","spiritual","worship"]},{"name":"medication_liquid","tags":["+","bottle","doctor","drug","health","hospital","liquid","medications","medicine","pharmacy","spoon","vessel"]},{"name":"camera_roll","tags":["camera","film","image","library","photo","photography","roll"]},{"name":"pin_end","tags":["action","arrow","dot","end","pin"]},{"name":"dialer_sip","tags":["alphabet","call","cell","character","contact","device","dialer","font","hardware","initiation","internet","letter","mobile","over","phone","protocol","routing","session","sip","symbol","telephone","text","type","voice"]},{"name":"oil_barrel","tags":["barrel","droplet","gas","gasoline","nest","oil","water"]},{"name":"disc_full","tags":["!","alert","attention","caution","cd","danger","disc","error","exclamation","full","important","mark","music","notification","storage","symbol","warning"]},{"name":"signal_cellular_connected_no_internet_4_bar","tags":["!","4","alert","attention","bar","caution","cell","cellular","connected","danger","data","error","exclamation","important","internet","mark","mobile","network","no","notification","phone","signal","symbol","warning","wifi","wireless"]},{"name":"wind_power","tags":["eco","energy","nest","power","wind","windy"]},{"name":"logo_dev","tags":["dev","dev.to","logo"]},{"name":"sledding","tags":["athlete","athletic","body","entertainment","exercise","hobby","human","people","person","sled","sledding","sledge","snow","social","sports","travel","winter"]},{"name":"invert_colors_off","tags":["colors","disabled","drop","droplet","enabled","hue","invert","inverted","off","offline","on","opacity","palette","slash","tone","water"]},{"name":"wifi_lock","tags":["cellular","connection","data","internet","lock","locked","mobile","network","password","privacy","private","protection","safety","secure","security","service","signal","wifi","wireless"]},{"name":"noise_aware","tags":["audio","aware","cancellation","music","noise","note","sound"]},{"name":"face_3","tags":["account","emoji","eyes","face","human","lock","log","login","logout","people","person","profile","recognition","security","social","thumbnail","unlock","user"]},{"name":"car_crash","tags":["accident","automobile","car","cars","collision","crash","direction","maps","public","transportation","vehicle"]},{"name":"comments_disabled","tags":["bubble","chat","comment","comments","communicate","disabled","enabled","feedback","message","off","offline","on","slash","speech"]},{"name":"data_array","tags":["array","brackets","code","coder","data","parentheses"]},{"name":"do_not_disturb_on_total_silence","tags":["busy","disturb","do","mute","no","not","on total","quiet","silence"]},{"name":"filter_b_and_w","tags":["and","b","black","contrast","edit","editing","effect","filter","grayscale","image","images","photography","picture","pictures","settings","w","white"]},{"name":"no_encryption_gmailerrorred","tags":["disabled","enabled","encryption","error","gmail","lock","locked","no","off","on","slash"]},{"name":"blur_linear","tags":["blur","dots","edit","editing","effect","enhance","filter","linear"]},{"name":"view_cozy","tags":["comfy","cozy","design","format","layout","view","web"]},{"name":"wifi_calling","tags":["call","calling","cell","connect","connection","connectivity","contact","device","hardware","mobile","phone","signal","telephone","wifi","wireless"]},{"name":"electric_rickshaw","tags":["automobile","car","cars","electric","india","maps","rickshaw","transportation","truck","vehicle"]},{"name":"rtt","tags":["call","real","rrt","text","time"]},{"name":"join_right","tags":["circle","command","join","matching","overlap","right","sql","values"]},{"name":"crop_3_2","tags":["2","3","adjust","adjustments","area","by","crop","edit","editing","frame","image","images","photo","photos","rectangle","settings","size","square"]},{"name":"crop_landscape","tags":["adjust","adjustments","area","crop","edit","editing","frame","image","images","landscape","photo","photos","settings","size"]},{"name":"nearby_error","tags":["!","alert","attention","caution","danger","error","exclamation","important","mark","nearby","notification","symbol","warning"]},{"name":"airplanemode_inactive","tags":["airplane","airplanemode","airport","disabled","enabled","flight","fly","inactive","maps","mode","off","offline","on","slash","transportation","travel"]},{"name":"airline_stops","tags":["airline","arrow","destination","direction","layover","location","maps","place","stops","transportation","travel","trip"]},{"name":"bluetooth_audio","tags":["audio","bluetooth","connect","connection","device","music","signal","sound","symbol"]},{"name":"portable_wifi_off","tags":["connection","data","disabled","enabled","internet","network","off","offline","on","portable","service","signal","slash","wifi","wireless"]},{"name":"turn_right","tags":["arrow","arrows","direction","directions","maps","navigation","path","right","route","sign","traffic","turn"]},{"name":"1x_mobiledata","tags":["1x","alphabet","cellular","character","digit","font","letter","mobile","mobiledata","network","number","phone","signal","speed","symbol","text","type","wifi"]},{"name":"do_not_step","tags":["boot","disabled","do","enabled","feet","foot","not","off","on","shoe","slash","sneaker","step","steps"]},{"name":"sensor_occupied","tags":["body","body response","connection","fitbit","human","network","people","person","scan","sensors","signal","smart body scan sensor","wireless"]},{"name":"directions_railway","tags":["automobile","car","cars","direction","directions","maps","public","railway","train","transportation","vehicle"]},{"name":"security_update_warning","tags":["!","Android","OS","alert","attention","caution","danger","device","download","error","exclamation","hardware","iOS","important","mark","mobile","notification","phone","security","symbol","tablet","update","warning"]},{"name":"pentagon","tags":["five sides","pentagon","shape"]},{"name":"wrap_text","tags":["arrow writing","doc","edit","editing","editor","sheet","spreadsheet","text","type","wrap","write","writing"]},{"name":"no_meeting_room","tags":["building","disabled","door","doorway","enabled","entrance","home","house","interior","meeting","no","off","office","on","open","places","room","slash"]},{"name":"sd_card_alert","tags":["!","alert","attention","camera","card","caution","danger","digital","error","exclamation","important","mark","memory","notification","photos","sd","secure","storage","symbol","warning"]},{"name":"deselect","tags":["all","disabled","enabled","off","on","selection","slash","square","tool"]},{"name":"switch_camera","tags":["arrow","arrows","camera","photo","photography","picture","switch"]},{"name":"text_rotate_up","tags":["A","alphabet","arrow","character","field","font","letter","move","rotate","symbol","text","type","up"]},{"name":"sync_lock","tags":["around","arrow","arrows","lock","locked","password","privacy","private","protection","renew","rotate","safety","secure","security","sync","turn"]},{"name":"switch_video","tags":["arrow","arrows","camera","photography","switch","video","videos"]},{"name":"border_clear","tags":["border","clear","doc","edit","editing","editor","sheet","spreadsheet","stroke","text","type","writing"]},{"name":"repeat_one_on","tags":["arrow","arrows","control","controls","digit","media","music","number","on","one","repeat","symbol","video"]},{"name":"no_meals","tags":["dining","disabled","eat","enabled","food","fork","knife","meal","meals","no","off","on","restaurant","slash","spoon","utensils"]},{"name":"align_vertical_top","tags":["align","alignment","format","layout","lines","paragraph","rule","rules","style","text","top","vertical"]},{"name":"subscript","tags":["2","doc","edit","editing","editor","gmail","novitas","sheet","spreadsheet","style","subscript","symbol","text","writing","x"]},{"name":"font_download_off","tags":["alphabet","character","disabled","download","enabled","font","letter","off","on","slash","square","symbol","text","type"]},{"name":"scoreboard","tags":["board","points","score","scoreboard","sports"]},{"name":"swipe_right_alt","tags":["accept","alt","arrows","direction","finger","hands","hit","navigation","right","strike","swing","swpie","take"]},{"name":"align_vertical_center","tags":["align","alignment","center","format","layout","lines","paragraph","rule","rules","style","text","vertical"]},{"name":"electric_meter","tags":["bolt","electric","energy","fast","lightning","measure","meter","nest","thunderbolt","usage","voltage","volts"]},{"name":"contact_emergency","tags":["account","avatar","call","cell","contacts","face","human","info","information","mobile","people","person","phone","profile","user"]},{"name":"signal_cellular_connected_no_internet_0_bar","tags":["!","0","alert","attention","bar","caution","cell","cellular","connected","danger","data","error","exclamation","important","internet","mark","mobile","network","no","notification","phone","signal","symbol","warning","wifi","wireless"]},{"name":"sim_card_alert","tags":["!","alert","attention","camera","card","caution","danger","digital","error","exclamation","important","mark","memory","notification","photos","sd","secure","storage","symbol","warning"]},{"name":"battery_2_bar","tags":["2","bar","battery","cell","charge","mobile","power"]},{"name":"text_rotation_angleup","tags":["A","alphabet","angleup","arrow","character","field","font","letter","move","rotate","symbol","text","type"]},{"name":"text_rotation_down","tags":["A","alphabet","arrow","character","dow","field","font","letter","move","rotate","symbol","text","type"]},{"name":"railway_alert","tags":["!","alert","attention","automobile","bike","car","cars","caution","danger","direction","error","exclamation","important","maps","mark","notification","public","railway","scooter","subway","symbol","train","transportation","vehicle","vespa","warning"]},{"name":"escalator","tags":["down","escalator","staircase","up"]},{"name":"electric_moped","tags":["automobile","bike","car","cars","electric","maps","moped","scooter","transportation","travel","vehicle","vespa"]},{"name":"closed_caption_disabled","tags":["accessible","alphabet","caption","cc","character","closed","decoder","disabled","enabled","font","language","letter","media","movies","off","on","slash","subtitle","subtitles","symbol","text","tv","type"]},{"name":"filter_7","tags":["7","digit","edit","editing","effect","filter","image","images","multiple","number","photography","picture","pictures","settings","stack","symbol"]},{"name":"heat_pump","tags":["air conditioner","cool","energy","furnance","heat","nest","pump","usage"]},{"name":"dry","tags":["air","bathroom","dry","dryer","fingers","gesture","hand","wc"]},{"name":"fork_right","tags":["arrow","arrows","direction","directions","fork","maps","navigation","path","right","route","sign","traffic"]},{"name":"text_rotation_angledown","tags":["A","alphabet","angledown","arrow","character","field","font","letter","move","rotate","symbol","text","type"]},{"name":"do_not_disturb_off","tags":["cancel","close","denied","deny","disabled","disturb","do","enabled","off","on","remove","silence","slash","stop"]},{"name":"screen_lock_portrait","tags":["Android","OS","device","hardware","iOS","lock","mobile","phone","portrait","rotate","screen","tablet"]},{"name":"send_time_extension","tags":["deliver","dispatch","envelop","extension","mail","message","schedule","send","time"]},{"name":"keyboard_command_key","tags":["button","command key","control","keyboard"]},{"name":"remove_from_queue","tags":["desktop","device","display","from","hardware","monitor","queue","remove","screen","steam"]},{"name":"filter_4","tags":["4","digit","edit","editing","effect","filter","image","images","multiple","number","photography","picture","pictures","settings","stack","symbol"]},{"name":"filter_9_plus","tags":["+","9","digit","edit","editing","effect","filter","image","images","multiple","number","photography","picture","pictures","plus","settings","stack","symbol"]},{"name":"exposure_plus_2","tags":["2","add","brightness","contrast","digit","edit","editing","effect","exposure","image","number","photo","photography","plus","settings","symbol"]},{"name":"surround_sound","tags":["circle","signal","sound","speaker","surround","system","volumn","wireless"]},{"name":"airline_seat_individual_suite","tags":["airline","body","business","class","first","human","individual","people","person","rest","seat","sleep","suite","travel"]},{"name":"home_max","tags":["device","gadget","hardware","home","internet","iot","max","nest","smart","things"]},{"name":"phone_paused","tags":["call","cell","contact","device","hardware","mobile","pause","paused","phone","telephone"]},{"name":"local_play","tags":[]},{"name":"stroller","tags":["baby","care","carriage","child","children","infant","kid","newborn","stroller","toddler","young"]},{"name":"wifi_password","tags":["(scan)","[cellular","connection","data","internet","lock","mobile]","network","password","secure","service","signal","wifi","wireless"]},{"name":"browse_gallery","tags":["clock","collection","gallery","library","stack","watch"]},{"name":"system_security_update","tags":["Android","OS","arrow","cell","device","down","hardware","iOS","mobile","phone","security","system","tablet","update"]},{"name":"person_2","tags":["account","face","human","people","person","profile","user"]},{"name":"screenshot_monitor","tags":["Android","OS","chrome","desktop","device","display","hardware","iOS","mac","monitor","screen","screengrab","screenshot","web","window"]},{"name":"wb_iridescent","tags":["balance","bright","edit","editing","iridescent","light","lighting","setting","settings","white","wp"]},{"name":"grid_off","tags":["collage","disabled","enabled","grid","image","layout","off","on","slash","view"]},{"name":"system_security_update_warning","tags":["!","Android","OS","alert","attention","caution","cell","danger","device","error","exclamation","hardware","iOS","important","mark","mobile","notification","phone","security","symbol","system","tablet","update","warning"]},{"name":"play_disabled","tags":["control","controls","disabled","enabled","media","music","off","on","play","slash","video"]},{"name":"php","tags":["alphabet","brackets","character","code","css","develop","developer","engineer","engineering","font","html","letter","php","platform","symbol","text","type"]},{"name":"phishing","tags":["fish","fishing","fraud","hook","phishing","scam"]},{"name":"border_style","tags":["border","color","doc","edit","editing","editor","sheet","spreadsheet","stroke","style","text","type","writing"]},{"name":"motion_photos_paused","tags":["animation","circle","motion","pause","paused","photos","video"]},{"name":"headphones_battery","tags":["accessory","audio","battery","charging","device","ear","earphone","headphones","headset","listen","music","sound"]},{"name":"monochrome_photos","tags":["black","camera","image","monochrome","photo","photography","photos","picture","white"]},{"name":"web_asset_off","tags":["asset","browser","disabled","enabled","internet","off","on","page","screen","slash","web","webpage","website","windows","www"]},{"name":"wifi_tethering_off","tags":["cell","cellular","connection","data","disabled","enabled","internet","mobile","network","off","offline","on","phone","scan","service","signal","slash","speed","tethering","wifi","wireless"]},{"name":"text_decrease","tags":["-","alphabet","character","decrease","font","letter","minus","remove","resize","subtract","symbol","text","type"]},{"name":"view_comfy_alt","tags":["alt","comfy","cozy","design","format","layout","view","web"]},{"name":"photo_camera_back","tags":["back","camera","image","landscape","mountain","mountains","photo","photography","picture","rear"]},{"name":"folder_off","tags":["data","disabled","doc","document","drive","enabled","file","folder","folders","off","on","online","sheet","slash","slide","storage"]},{"name":"gas_meter","tags":["droplet","energy","gas","measure","meter","nest","usage","water"]},{"name":"edgesensor_high","tags":["Android","OS","cell","device","edge","hardware","high","iOS","mobile","move","phone","sensitivity","sensor","tablet","vibrate"]},{"name":"filter_5","tags":["5","digit","edit","editing","effect","filter","image","images","multiple","number","photography","picture","pictures","settings","stack","symbol"]},{"name":"stay_current_landscape","tags":["Android","OS","current","device","hardware","iOS","landscape","mobile","phone","stay","tablet"]},{"name":"sip","tags":["alphabet","call","character","dialer","font","initiation","internet","letter","over","phone","protocol","routing","session","sip","symbol","text","type","voice"]},{"name":"power_input","tags":["input","lines","power","supply"]},{"name":"smart_screen","tags":["Android","OS","airplay","cast","cell","connect","device","hardware","iOS","mobile","phone","screen","screencast","smart","stream","tablet","video"]},{"name":"mail_lock","tags":["email","envelop","letter","lock","locked","mail","message","password","privacy","private","protection","safety","secure","security","send"]},{"name":"dataset","tags":[]},{"name":"nat","tags":["communication","nat"]},{"name":"do_disturb_off","tags":["cancel","close","denied","deny","disabled","disturb","do","enabled","off","on","remove","silence","slash","stop"]},{"name":"no_drinks","tags":["alcohol","beverage","bottle","cocktail","drink","drinks","food","liquor","no","wine"]},{"name":"bike_scooter","tags":["automobile","bike","car","cars","maps","scooter","transportation","vehicle","vespa"]},{"name":"dock","tags":["Android","OS","cell","charging","connector","device","dock","hardware","iOS","mobile","phone","power","station","tablet"]},{"name":"face_2","tags":["account","emoji","eyes","face","human","lock","log","login","logout","people","person","profile","recognition","security","social","thumbnail","unlock","user"]},{"name":"face_retouching_off","tags":["disabled","edit","editing","effect","emoji","emotion","enabled","face","faces","image","natural","off","on","photo","photography","retouch","retouching","settings","slash","tag"]},{"name":"auto_fix_off","tags":["ai","artificial","auto","automatic","automation","custom","disabled","edit","enabled","erase","fix","genai","intelligence","magic","modify","off","on","slash","smart","spark","sparkle","star","wand"]},{"name":"airline_seat_flat","tags":["airline","body","business","class","first","flat","human","people","person","rest","seat","sleep","travel"]},{"name":"phone_locked","tags":["call","cell","contact","device","hardware","lock","locked","mobile","password","phone","privacy","private","protection","safety","secure","security","telephone"]},{"name":"network_locked","tags":["alert","available","cellular","connection","data","error","internet","lock","locked","mobile","network","not","privacy","private","protection","restricted","safety","secure","security","service","signal","warning","wifi","wireless"]},{"name":"padding","tags":["design","layout","margin","padding","size","square"]},{"name":"browser_not_supported","tags":["browser","disabled","enabled","internet","not","off","on","page","screen","site","slash","supported","web","website","www"]},{"name":"border_outer","tags":["border","doc","edit","editing","editor","outer","sheet","spreadsheet","stroke","text","type","writing"]},{"name":"exposure_neg_1","tags":["1","brightness","contrast","digit","edit","editing","effect","exposure","image","neg","negative","number","photo","photography","settings","symbol"]},{"name":"view_compact_alt","tags":["alt","compact","design","format","layout dense","view","web"]},{"name":"pest_control_rodent","tags":["control","exterminator","mice","pest","rodent"]},{"name":"swipe_down_alt","tags":["alt","arrows","direction","disable","down","enable","finger","hands","hit","navigation","strike","swing","swpie","take"]},{"name":"airlines","tags":["airlines","airplane","airport","flight","plane","transportation","travel","trip"]},{"name":"turn_left","tags":["arrow","arrows","direction","directions","left","maps","navigation","path","route","sign","traffic","turn"]},{"name":"sd","tags":["alphabet","camera","card","character","data","device","digital","drive","flash","font","image","letter","memory","photo","sd","secure","symbol","text","type"]},{"name":"near_me_disabled","tags":["destination","direction","disabled","enabled","location","maps","me","navigation","near","off","on","pin","place","point","slash"]},{"name":"face_4","tags":["account","emoji","eyes","face","human","lock","log","login","logout","people","person","profile","recognition","security","social","thumbnail","unlock","user"]},{"name":"stay_primary_landscape","tags":["Android","OS","current","device","hardware","iOS","landscape","mobile","phone","primary","stay","tablet"]},{"name":"4g_plus_mobiledata","tags":["4g","alphabet","cellular","character","digit","font","letter","mobile","mobiledata","network","number","phone","plus","signal","speed","symbol","text","type","wifi"]},{"name":"snowmobile","tags":["automobile","car","direction","skimobile","snow","snowmobile","social","sports","transportation","travel","vehicle","winter"]},{"name":"sign_language","tags":["communication","deaf","fingers","gesture","hand","language","sign"]},{"name":"network_ping","tags":["alert","available","cellular","connection","data","internet","ip","mobile","network","ping","service","signal","wifi","wireless"]},{"name":"signal_cellular_off","tags":["cell","cellular","data","disabled","enabled","internet","mobile","network","off","offline","on","phone","signal","slash","wifi","wireless"]},{"name":"signal_cellular_nodata","tags":["cell","cellular","data","internet","mobile","network","no","nodata","offline","phone","quit","signal","wifi","wireless","x"]},{"name":"no_sim","tags":["camera","card","device","eject","insert","memory","no","phone","sim","storage"]},{"name":"signal_wifi_4_bar_lock","tags":["4","bar","cell","cellular","data","internet","lock","locked","mobile","network","password","phone","privacy","private","protection","safety","secure","security","signal","wifi","wireless"]},{"name":"missed_video_call","tags":["arrow","call","camera","film","filming","hardware","image","missed","motion","picture","record","video","videography"]},{"name":"lte_mobiledata","tags":["alphabet","character","data","font","internet","letter","lte","mobile","network","speed","symbol","text","type","wifi","wireless"]},{"name":"earbuds_battery","tags":["accessory","audio","battery","charging","earbuds","earphone","headphone","listen","music","sound"]},{"name":"panorama_photosphere","tags":["angle","horizontal","image","panorama","photo","photography","photosphere","picture","wide"]},{"name":"no_crash","tags":["accident","auto","automobile","car","cars","check","collision","confirm","correct","crash","direction","done","enter","maps","mark","no","ok","okay","select","tick","transportation","vehicle","yes"]},{"name":"add_alarm","tags":[]},{"name":"directions_transit_filled","tags":["automobile","car","cars","direction","directions","filled","maps","public","rail","subway","train","transit","transportation","vehicle"]},{"name":"u_turn_left","tags":["arrow","arrows","direction","directions","left","maps","navigation","path","route","sign","traffic","u-turn"]},{"name":"line_axis","tags":["axis","dash","horizontal","line","stroke","vertical"]},{"name":"density_large","tags":["density","horizontal","large","lines","rule","rules"]},{"name":"location_disabled","tags":["destination","direction","disabled","enabled","location","maps","off","on","pin","place","pointer","slash","stop","tracking"]},{"name":"bluetooth_drive","tags":["automobile","bluetooth","car","cars","cast","connect","connection","device","drive","maps","paring","streaming","symbol","transportation","travel","vehicle","wireless"]},{"name":"30fps","tags":["30fps","alphabet","camera","character","digit","font","fps","frames","letter","number","symbol","text","type","video"]},{"name":"no_luggage","tags":["bag","baggage","carry","disabled","enabled","luggage","no","off","on","slash","suitcase","travel"]},{"name":"leak_remove","tags":["connection","data","disabled","enabled","leak","link","network","off","offline","on","remove","service","signals","slash","synce","wireless"]},{"name":"filter_8","tags":["8","digit","edit","editing","effect","filter","image","images","multiple","number","photography","picture","pictures","settings","stack","symbol"]},{"name":"mobile_off","tags":["Android","OS","cell","device","disabled","enabled","hardware","iOS","mobile","off","on","phone","silence","slash","tablet"]},{"name":"key_off","tags":["disabled","enabled","key","lock","off","offline","on","password","slash","unlock"]},{"name":"signal_cellular_null","tags":["cell","cellular","data","internet","mobile","network","null","phone","signal","wifi","wireless"]},{"name":"phonelink_off","tags":["Android","OS","chrome","computer","connect","desktop","device","disabled","enabled","hardware","iOS","link","mac","mobile","off","on","phone","phonelink","slash","sync","tablet","web","windows"]},{"name":"filter_9","tags":["9","digit","edit","editing","effect","filter","image","images","multiple","number","photography","picture","pictures","settings","stack","symbol"]},{"name":"home_mini","tags":["Internet","device","gadget","hardware","home","iot","mini","nest","smart","things"]},{"name":"on_device_training","tags":["arrow","bulb","call","cell","contact","device","hardware","idea","inprogress","light","load","loading","mobile","model","phone","refresh","renew","restore","reverse","rotate","telephone","training"]},{"name":"egg_alt","tags":["breakfast","brunch","egg","food"]},{"name":"media_bluetooth_on","tags":["bluetooth","connect","connection","connectivity","device","disabled","enabled","media","music","note","off","on","online","paring","signal","slash","symbol","wireless"]},{"name":"10k","tags":["10000","10K","alphabet","character","digit","display","font","letter","number","pixel","pixels","resolution","symbol","text","type","video"]},{"name":"video_stable","tags":["film","filming","recording","setting","stability","stable","taping","video"]},{"name":"add_home","tags":[]},{"name":"no_transfer","tags":["automobile","bus","car","cars","direction","disabled","enabled","maps","no","off","on","public","slash","transfer","transportation","vehicle"]},{"name":"timer_10","tags":["10","digits","duration","number","numbers","seconds","time","timer"]},{"name":"directions_subway_filled","tags":["automobile","car","cars","direction","directions","filled","maps","public","rail","subway","train","transportation","vehicle"]},{"name":"wb_shade","tags":["balance","house","light","lighting","shade","wb","white"]},{"name":"swipe_left_alt","tags":["alt","arrow","arrows","finger","hand","hit","left","navigation","reject","strike","swing","swipe","take"]},{"name":"filter_6","tags":["6","digit","edit","editing","effect","filter","image","images","multiple","number","photography","picture","pictures","settings","stack","symbol"]},{"name":"cyclone","tags":["crisis","disaster","natural","rain","storm","weather","wind","winds"]},{"name":"network_wifi_1_bar","tags":[]},{"name":"directions_railway_filled","tags":["automobile","car","cars","direction","directions","filled","maps","public","railway","train","transportation","vehicle"]},{"name":"wifi_find","tags":["(scan)","[cellular","connection","data","detect","discover","find","internet","look","magnifying glass","mobile]","network","notice","search","service","signal","wifi","wireless"]},{"name":"blur_off","tags":["blur","disabled","dots","edit","editing","effect","enabled","enhance","off","on","slash"]},{"name":"motion_photos_off","tags":["animation","circle","disabled","enabled","motion","off","on","photos","slash","video"]},{"name":"lyrics","tags":["audio","bubble","chat","comment","communicate","feedback","key","lyrics","message","music","note","song","sound","speech","track"]},{"name":"raw_on","tags":["alphabet","character","disabled","enabled","font","image","letter","off","on","original","photo","photography","raw","slash","symbol","text","type"]},{"name":"flight_class","tags":["airplane","business","class","first","flight","plane","seat","transportation","travel","trip","window"]},{"name":"insert_page_break","tags":["break","doc","document","file","page","paper"]},{"name":"rsvp","tags":["alphabet","character","font","invitation","invite","letter","plaît","respond","rsvp","répondez","sil","symbol","text","type","vous"]},{"name":"tire_repair","tags":["auto","automobile","car","cars","gauge","mechanic","pressure","repair","tire","vehicle"]},{"name":"swipe_up_alt","tags":["alt","arrows","direction","disable","enable","finger","hands","hit","navigation","strike","swing","swpie","take","up"]},{"name":"3g_mobiledata","tags":["3g","alphabet","cellular","character","digit","font","letter","mobile","mobiledata","network","number","phone","signal","speed","symbol","text","type","wifi"]},{"name":"tv_off","tags":["Android","OS","chrome","desktop","device","disabled","enabled","hardware","iOS","mac","monitor","off","on","slash","television","tv","web","window"]},{"name":"hdr_on","tags":["add","alphabet","character","dynamic","enhance","font","hdr","high","letter","on","plus","range","select","symbol","text","type"]},{"name":"add_home_work","tags":[]},{"name":"motion_photos_pause","tags":["animation","circle","motion","pause","paused","photos","video"]},{"name":"edgesensor_low","tags":["Android","cell","device","edge","hardware","iOS","low","mobile","move","phone","sensitivity","sensor","tablet","vibrate"]},{"name":"grid_goldenratio","tags":["golden","goldenratio","grid","layout","lines","ratio","space"]},{"name":"network_wifi_3_bar","tags":[]},{"name":"temple_buddhist","tags":["buddha","buddhism","buddhist","monastery","religion","spiritual","temple","worship"]},{"name":"airline_seat_flat_angled","tags":["airline","angled","body","business","class","first","flat","human","people","person","rest","seat","sleep","travel"]},{"name":"fort","tags":["castle","fort","fortress","mansion","palace"]},{"name":"spatial_tracking","tags":["audio","disabled","enabled","music","note","off","offline","on","slash","sound","spatial","tracking"]},{"name":"screen_lock_rotation","tags":["Android","OS","arrow","device","hardware","iOS","lock","mobile","phone","rotate","rotation","screen","tablet","turn"]},{"name":"fiber_pin","tags":["alphabet","character","fiber","font","letter","network","pin","symbol","text","type"]},{"name":"phone_bluetooth_speaker","tags":["bluetooth","call","cell","connect","connection","connectivity","contact","device","hardware","mobile","phone","signal","speaker","symbol","telephone","wireless"]},{"name":"vignette","tags":["border","edit","editing","filter","gradient","image","photo","photography","setting","vignette"]},{"name":"panorama_horizontal","tags":["angle","horizontal","image","panorama","photo","photography","picture","wide"]},{"name":"propane_tank","tags":["bbq","gas","grill","nest","propane","tank"]},{"name":"kebab_dining","tags":["dining","dinner","food","kebab","meal","meat","skewer"]},{"name":"developer_board_off","tags":["board","chip","computer","developer","development","disabled","enabled","hardware","microchip","off","on","processor","slash"]},{"name":"adf_scanner","tags":["adf","document","feeder","machine","office","scan","scanner"]},{"name":"no_cell","tags":["Android","OS","cell","device","disabled","enabled","hardware","iOS","mobile","no","off","on","phone","slash","tablet"]},{"name":"dirty_lens","tags":["camera","dirty","lens","photo","photography","picture","splat"]},{"name":"usb_off","tags":["cable","connection","device","off","usb","wire"]},{"name":"image_aspect_ratio","tags":["aspect","image","photo","photography","picture","ratio","rectangle","square"]},{"name":"30fps_select","tags":["30","camera","digits","fps","frame","frequency","image","numbers","per","rate","second","seconds","select","video"]},{"name":"60fps","tags":["60fps","camera","digit","fps","frames","number","symbol","video"]},{"name":"screen_lock_landscape","tags":["Android","OS","device","hardware","iOS","landscape","lock","mobile","phone","rotate","screen","tablet"]},{"name":"lte_plus_mobiledata","tags":["+","alphabet","character","data","font","internet","letter","lte","mobile","network","plus","speed","symbol","text","type","wifi","wireless"]},{"name":"piano_off","tags":["disabled","enabled","instrument","keyboard","keys","music","musical","off","on","piano","slash","social"]},{"name":"unfold_more_double","tags":["arrow","arrows","chevron","collapse","direction","double","down","expand","expandable","list","more","navigation","unfold"]},{"name":"deblur","tags":["adjust","deblur","edit","editing","enhance","face","image","lines","photo","photography","sharpen"]},{"name":"person_4","tags":["account","face","human","people","person","profile","user"]},{"name":"spatial_audio","tags":["audio","music","note","sound","spatial"]},{"name":"camera_rear","tags":["camera","front","lens","mobile","phone","photo","photography","picture","portrait","rear","selfie"]},{"name":"timer_10_select","tags":["10","alphabet","camera","character","digit","font","letter","number","seconds","select","symbol","text","timer","type"]},{"name":"face_5","tags":["account","emoji","eyes","face","human","lock","log","login","logout","people","person","profile","recognition","security","social","thumbnail","unlock","user"]},{"name":"minor_crash","tags":["accident","auto","automobile","car","cars","collision","directions","maps","public","transportation","vehicle"]},{"name":"sos","tags":["font","help","letters","save","sos","text","type"]},{"name":"videogame_asset_off","tags":["asset","console","controller","device","disabled","enabled","game","gamepad","gaming","off","on","playstation","slash","video","videogame"]},{"name":"flood","tags":["crisis","disaster","natural","rain","storm","weather"]},{"name":"60fps_select","tags":["60","camera","digits","fps","frame","frequency","numbers","per","rate","second","seconds","select","video"]},{"name":"timer_3","tags":["3","digits","duration","number","numbers","seconds","time","timer"]},{"name":"vpn_key_off","tags":["code","disabled","enabled","key","lock","network","off","offline","on","passcode","password","slash","unlock","vpn"]},{"name":"directions_off","tags":["arrow","directions","disabled","enabled","maps","off","on","right","route","sign","slash","traffic"]},{"name":"emergency_share","tags":["alert","attention","caution","danger","emergency","important","notification","share","warning"]},{"name":"panorama_wide_angle_select","tags":["angle","image","panorama","photo","photography","picture","select","wide"]},{"name":"airline_seat_legroom_normal","tags":["airline","body","feet","human","leg","legroom","normal","people","person","seat","sitting","space","travel"]},{"name":"fiber_dvr","tags":["alphabet","character","digital","dvr","electronics","fiber","font","letter","network","record","recorder","symbol","text","tv","type","video"]},{"name":"person_3","tags":["account","face","human","people","person","profile","user"]},{"name":"scuba_diving","tags":["diving","entertainment","exercise","hobby","scuba","social","swim","swimming"]},{"name":"signal_cellular_no_sim","tags":["camera","card","cellular","chip","device","disabled","enabled","memory","no","off","offline","on","phone","signal","sim","slash","storage"]},{"name":"24mp","tags":["24","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"exposure_neg_2","tags":["2","brightness","contrast","digit","edit","editing","effect","exposure","image","neg","negative","number","photo","photography","settings","symbol"]},{"name":"network_wifi_2_bar","tags":[]},{"name":"wifi_2_bar","tags":["2","bar","cell","cellular","connection","data","internet","mobile","network","phone","scan","service","signal","wifi","wireless"]},{"name":"u_turn_right","tags":["arrow","arrows","direction","directions","maps","navigation","path","right","route","sign","traffic","u-turn"]},{"name":"currency_yuan","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","money","online","pay","payment","price","shopping","symbol","yuan"]},{"name":"currency_lira","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","lira","money","online","pay","payment","price","shopping","symbol"]},{"name":"no_flash","tags":["bolt","camera","disabled","enabled","flash","image","lightning","no","off","on","photo","photography","picture","slash","thunderbolt"]},{"name":"temple_hindu","tags":["hindu","hinduism","hindus","mandir","religion","spiritual","temple","worship"]},{"name":"mode_fan_off","tags":["air conditioner","cool","disabled","enabled","fan","nest","off","on","slash"]},{"name":"airline_seat_legroom_extra","tags":["airline","body","extra","feet","human","leg","legroom","people","person","seat","sitting","space","travel"]},{"name":"4k_plus","tags":["+","4000","4K","alphabet","character","digit","display","font","letter","number","pixel","pixels","plus","resolution","symbol","text","type","video"]},{"name":"border_inner","tags":["border","doc","edit","editing","editor","inner","sheet","spreadsheet","stroke","text","type","writing"]},{"name":"wifi_tethering_error","tags":["!","alert","attention","caution","cell","cellular","connection","danger","data","error","exclamation","important","internet","mark","mobile","network","notification","phone","rounded","scan","service","signal","speed","symbol","tethering","warning","wifi","wireless"]},{"name":"airline_seat_legroom_reduced","tags":["airline","body","feet","human","leg","legroom","people","person","reduced","seat","sitting","space","travel"]},{"name":"synagogue","tags":["jew","jewish","religion","shul","spiritual","temple","worship"]},{"name":"border_left","tags":["border","doc","edit","editing","editor","left","sheet","spreadsheet","stroke","text","type","writing"]},{"name":"autofps_select","tags":["A","alphabet","auto","character","font","fps","frame","frequency","letter","per","rate","second","seconds","select","symbol","text","type"]},{"name":"signal_cellular_alt_2_bar","tags":["2","bar","cell","cellular","data","internet","mobile","network","phone","signal","speed","wifi","wireless"]},{"name":"g_mobiledata","tags":["alphabet","character","data","font","g","letter","mobile","network","service","symbol","text","type"]},{"name":"1k","tags":["1000","1K","alphabet","character","digit","display","font","letter","number","pixel","pixels","resolution","symbol","text","type","video"]},{"name":"format_textdirection_l_to_r","tags":["align","alignment","doc","edit","editing","editor","format","ltr","sheet","spreadsheet","text","textdirection","type","writing"]},{"name":"border_bottom","tags":["border","bottom","doc","edit","editing","editor","sheet","spreadsheet","stroke","text","type","writing"]},{"name":"fork_left","tags":["arrow","arrows","direction","directions","fork","left","maps","navigation","path","route","sign","traffic"]},{"name":"severe_cold","tags":["!","alert","attention","caution","climate","cold","crisis","danger","disaster","error","exclamation","important","notification","severe","snow","snowflake","warning","weather","winter"]},{"name":"tsunami","tags":["crisis","disaster","flood","rain","storm","tsunami","weather"]},{"name":"signal_cellular_alt_1_bar","tags":["1","bar","cell","cellular","data","internet","mobile","network","phone","signal","speed","wifi","wireless"]},{"name":"border_vertical","tags":["border","doc","edit","editing","editor","sheet","spreadsheet","stroke","text","type","vertical","writing"]},{"name":"turn_sharp_right","tags":["arrow","arrows","direction","directions","maps","navigation","path","right","route","sharp","sign","traffic","turn"]},{"name":"no_backpack","tags":["accessory","backpack","bag","bookbag","knapsack","no","pack","travel"]},{"name":"remove_road","tags":["-","cancel","close","destination","direction","exit","highway","maps","minus","new","no","remove","road","stop","street","symbol","traffic","x"]},{"name":"timer_3_select","tags":["3","alphabet","camera","character","digit","font","letter","number","seconds","select","symbol","text","timer","type"]},{"name":"roller_skating","tags":["athlete","athletic","entertainment","exercise","hobby","roller","shoe","skate","skates","skating","social","sports","travel"]},{"name":"panorama_horizontal_select","tags":["angle","horizontal","image","panorama","photo","photography","picture","select","wide"]},{"name":"border_horizontal","tags":["border","doc","edit","editing","editor","horizontal","sheet","spreadsheet","stroke","text","type","writing"]},{"name":"2k","tags":["2000","2K","alphabet","character","digit","display","font","letter","number","pixel","pixels","resolution","symbol","text","type","video"]},{"name":"wifi_1_bar","tags":["1","bar","cell","cellular","connection","data","internet","mobile","network","phone","scan","service","signal","wifi","wireless"]},{"name":"format_textdirection_r_to_l","tags":["align","alignment","doc","edit","editing","editor","format","rtl","sheet","spreadsheet","text","textdirection","type","writing"]},{"name":"wifi_channel","tags":["(scan)","[cellular","channel","connection","data","internet","mobile]","network","service","signal","wifi","wireless"]},{"name":"roundabout_right","tags":["arrow","arrows","direction","directions","maps","navigation","path","right","roundabout","route","sign","traffic"]},{"name":"wb_auto","tags":["A","W","alphabet","auto","automatic","balance","character","edit","editing","font","image","letter","photo","photography","symbol","text","type","white","wp"]},{"name":"panorama_photosphere_select","tags":["angle","horizontal","image","panorama","photo","photography","photosphere","picture","select","wide"]},{"name":"panorama_wide_angle","tags":["angle","image","panorama","photo","photography","picture","wide"]},{"name":"hdr_plus","tags":["+","add","alphabet","character","circle","dynamic","enhance","font","hdr","high","letter","plus","range","select","symbol","text","type"]},{"name":"panorama_vertical_select","tags":["angle","image","panorama","photo","photography","picture","select","vertical","wide"]},{"name":"border_top","tags":["border","doc","edit","editing","editor","sheet","spreadsheet","stroke","text","top","type","writing"]},{"name":"mic_external_off","tags":["audio","disabled","enabled","external","mic","microphone","off","on","slash","sound","voice"]},{"name":"width_full","tags":[]},{"name":"h_mobiledata","tags":["alphabet","character","data","font","h","letter","mobile","network","service","symbol","text","type"]},{"name":"roller_shades","tags":["blinds","cover","curtains","nest","open","roller","shade","shutter","sunshade"]},{"name":"no_stroller","tags":["baby","care","carriage","child","children","disabled","enabled","infant","kid","newborn","no","off","on","parents","slash","stroller","toddler","young"]},{"name":"tornado","tags":["crisis","disaster","natural","rain","storm","tornado","weather","wind"]},{"name":"keyboard_control_key","tags":["control key","keyboard"]},{"name":"turn_slight_right","tags":["arrow","arrows","direction","directions","maps","navigation","path","right","route","sharp","sign","slight","traffic","turn"]},{"name":"border_right","tags":["border","doc","edit","editing","editor","right","sheet","spreadsheet","stroke","text","type","writing"]},{"name":"1k_plus","tags":["+","1000","1K","alphabet","character","digit","display","font","letter","number","pixel","pixels","plus","resolution","symbol","text","type","video"]},{"name":"turn_slight_left","tags":["arrow","arrows","direction","directions","maps","navigation","path","right","route","sign","slight","traffic","turn"]},{"name":"screen_rotation_alt","tags":["Android","OS","arrow","device","hardware","iOS","mobile","phone","rotate","rotation","screen","tablet","turn"]},{"name":"dataset_linked","tags":[]},{"name":"unfold_less_double","tags":["arrow","arrows","chevron","collapse","direction","double","expand","expandable","inward","less","list","navigation","unfold","up"]},{"name":"8k","tags":["8000","8K","alphabet","character","digit","display","font","letter","number","pixel","pixels","resolution","symbol","text","type","video"]},{"name":"landslide","tags":["crisis","disaster","natural","rain","storm","weather"]},{"name":"media_bluetooth_off","tags":["bluetooth","connect","connection","connectivity","device","disabled","enabled","media","music","note","off","offline","on","paring","signal","slash","symbol","wireless"]},{"name":"fire_truck","tags":[]},{"name":"e_mobiledata","tags":["alphabet","data","e","font","letter","mobile","mobiledata","text","type"]},{"name":"panorama_vertical","tags":["angle","image","panorama","photo","photography","picture","vertical","wide"]},{"name":"r_mobiledata","tags":["alphabet","character","data","font","letter","mobile","r","symbol","text","type"]},{"name":"12mp","tags":["12","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"repartition","tags":["arrow","arrows","data","partition","refresh","renew","repartition","restore","table"]},{"name":"width_normal","tags":[]},{"name":"h_plus_mobiledata","tags":["+","alphabet","character","data","font","h","letter","mobile","network","plus","service","symbol","text","type"]},{"name":"hdr_enhanced_select","tags":["add","alphabet","character","dynamic","enhance","font","hdr","high","letter","plus","range","select","symbol","text","type"]},{"name":"mp","tags":["alphabet","character","font","image","letter","megapixel","mp","photo","photography","pixels","quality","resolution","symbol","text","type"]},{"name":"shape_line","tags":["circle","draw","edit","editing","line","shape","square"]},{"name":"9k_plus","tags":["+","9000","9K","alphabet","character","digit","display","font","letter","number","pixel","pixels","plus","resolution","symbol","text","type","video"]},{"name":"5k","tags":["5000","5K","alphabet","character","digit","display","font","letter","number","pixel","pixels","resolution","symbol","text","type","video"]},{"name":"hevc","tags":["alphabet","character","coding","efficiency","font","hevc","high","letter","symbol","text","type","video"]},{"name":"currency_franc","tags":["bill","card","cash","coin","commerce","cost","credit","currency","dollars","finance","franc","money","online","pay","payment","price","shopping","symbol"]},{"name":"8k_plus","tags":["+","7000","8K","alphabet","character","digit","display","font","letter","number","pixel","pixels","plus","resolution","symbol","text","type","video"]},{"name":"hdr_on_select","tags":["+","alphabet","camera","character","circle","dynamic","font","hdr","high","letter","on","photo","range","select","symbol","text","type"]},{"name":"3k","tags":["3000","3K","alphabet","character","digit","display","font","letter","number","pixel","pixels","resolution","symbol","text","type","video"]},{"name":"transcribe","tags":[]},{"name":"width_wide","tags":[]},{"name":"hdr_auto_select","tags":["+","A","alphabet","auto","camera","character","circle","dynamic","font","hdr","high","letter","photo","range","select","symbol","text","type"]},{"name":"hls","tags":["alphabet","character","develop","developer","engineer","engineering","font","hls","letter","platform","symbol","text","type"]},{"name":"5k_plus","tags":["+","5000","5K","alphabet","character","digit","display","font","letter","number","pixel","pixels","plus","resolution","symbol","text","type","video"]},{"name":"assist_walker","tags":["accessibility","accessible","assist","body","disability","handicap","help","human","injured","injury","mobility","person","walk","walker"]},{"name":"hls_off","tags":["alphabet","character","develop","developer","disabled","enabled","engineer","engineering","font","hls","letter","off","offline","on","platform","slash","symbol","text","type"]},{"name":"18mp","tags":["18","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"format_overline","tags":["alphabet","character","doc","edit","editing","editor","font","format","letter","line","overline","sheet","spreadsheet","style","symbol","text","type","under","writing"]},{"name":"volcano","tags":["crisis","disaster","eruption","lava","magma","natural","volcano"]},{"name":"vaping_rooms","tags":["allowed","e-cigarette","never","no","places","prohibited","smoke","smoking","tobacco","vape","vaping","vapor","warning","zone"]},{"name":"watch_off","tags":["Android","OS","ar","clock","close","gadget","iOS","off","shut","time","vr","watch","wearables","web","wristwatch"]},{"name":"9k","tags":["9000","9K","alphabet","character","digit","display","font","letter","number","pixel","pixels","resolution","symbol","text","type","video"]},{"name":"23mp","tags":["23","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"propane","tags":["gas","nest","propane"]},{"name":"raw_off","tags":["alphabet","character","disabled","enabled","font","image","letter","off","on","original","photo","photography","raw","slash","symbol","text","type"]},{"name":"keyboard_option_key","tags":["alt key","key","keyboard","modifier key","option"]},{"name":"woman_2","tags":["female","gender","girl","lady","social","symbol","woman","women"]},{"name":"2k_plus","tags":["+","2k","alphabet","character","digit","font","letter","number","plus","symbol","text","type"]},{"name":"6k_plus","tags":["+","6000","6K","alphabet","character","digit","display","font","letter","number","pixel","pixels","plus","resolution","symbol","text","type","video"]},{"name":"broadcast_on_personal","tags":[]},{"name":"10mp","tags":["10","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"man_2","tags":["boy","gender","male","man","social","symbol"]},{"name":"7k","tags":["7000","7K","alphabet","character","digit","display","font","letter","number","pixel","pixels","resolution","symbol","text","type","video"]},{"name":"7k_plus","tags":["+","7000","7K","alphabet","character","digit","display","font","letter","number","pixel","pixels","plus","resolution","symbol","text","type","video"]},{"name":"nearby_off","tags":["disabled","enabled","nearby","off","on","slash"]},{"name":"3k_plus","tags":["+","3000","3K","alphabet","character","digit","display","font","letter","number","pixel","pixels","plus","resolution","symbol","text","type","video"]},{"name":"6k","tags":["6000","6K","alphabet","character","digit","display","font","letter","number","pixel","pixels","resolution","symbol","text","type","video"]},{"name":"hdr_off","tags":["alphabet","character","disabled","dynamic","enabled","enhance","font","hdr","high","letter","off","on","range","select","slash","symbol","text","type"]},{"name":"roundabout_left","tags":["arrow","arrows","direction","directions","left","maps","navigation","path","roundabout","route","sign","traffic"]},{"name":"hdr_off_select","tags":["alphabet","camera","character","circle","disabled","dynamic","enabled","font","hdr","high","letter","off","on","photo","range","select","slash","symbol","text","type"]},{"name":"bedtime_off","tags":["bedtime","disabled","lunar","moon","night","nightime","off","offline","slash","sleep"]},{"name":"18_up_rating","tags":[]},{"name":"turn_sharp_left","tags":["arrow","arrows","direction","directions","left","maps","navigation","path","route","sharp","sign","traffic","turn"]},{"name":"11mp","tags":["11","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"roller_shades_closed","tags":["blinds","closed","cover","curtains","nest","roller","shade","shutter","sunshade"]},{"name":"20mp","tags":["20","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"blinds","tags":["blinds","cover","curtains","nest","open","shade","shutter","sunshade"]},{"name":"3mp","tags":["3","camera","digit","font","image","letters","megapixel","megapixels","mp","number","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"blind","tags":["accessibility","accessible","assist","blind","body","cane","disability","handicap","help","human","mobility","person","walk","walker"]},{"name":"emergency_recording","tags":["alert","attention","camera","caution","danger","emergency","film","filming","hardware","image","important","motion","notification","picture","record","video","videography","warning"]},{"name":"curtains","tags":["blinds","cover","curtains","nest","open","shade","shutter","sunshade"]},{"name":"13mp","tags":["13","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"5mp","tags":["5","camera","digit","font","image","letters","megapixel","megapixels","mp","number","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"21mp","tags":["21","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"blinds_closed","tags":["blinds","closed","cover","curtains","nest","shade","shutter","sunshade"]},{"name":"16mp","tags":["16","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"17mp","tags":["17","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"2mp","tags":["2","camera","digit","font","image","letters","megapixel","megapixels","mp","number","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"15mp","tags":["15","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"desk","tags":[]},{"name":"no_adult_content","tags":[]},{"name":"14mp","tags":["14","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"22mp","tags":["22","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"vertical_shades","tags":["blinds","cover","curtains","nest","open","shade","shutter","sunshade","vertical"]},{"name":"vertical_shades_closed","tags":["blinds","closed","cover","curtains","nest","roller","shade","shutter","sunshade"]},{"name":"curtains_closed","tags":["blinds","closed","cover","curtains","nest","shade","shutter","sunshade"]},{"name":"broadcast_on_home","tags":[]},{"name":"4mp","tags":["4","camera","digit","font","image","letters","megapixel","megapixels","mp","number","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"19mp","tags":["19","camera","digits","font","image","letters","megapixel","megapixels","mp","numbers","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"nest_cam_wired_stand","tags":["camera","film","filming","hardware","image","motion","nest","picture","stand","video","videography","wired"]},{"name":"9mp","tags":["9","camera","digit","font","image","letters","megapixel","megapixels","mp","number","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"7mp","tags":["7","camera","digit","font","image","letters","megapixel","megapixels","mp","number","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"8mp","tags":["8","camera","digit","font","image","letters","megapixel","megapixels","mp","number","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"6mp","tags":["6","camera","digit","font","image","letters","megapixel","megapixels","mp","number","pixel","pixels","quality","resolution","symbol","text","type"]},{"name":"devices_fold","tags":["Android","OS","cell","device","fold","foldable","hardware","iOS","mobile","phone","tablet"]},{"name":"vape_free","tags":["disabled","e-cigarette","enabled","free","never","no","off","on","places","prohibited","slash","smoke","smoking","tobacco","vape","vaping","vapor","warning","zone"]},{"name":"ramp_left","tags":["arrow","arrows","direction","directions","left","maps","navigation","path","ramp","route","sign","traffic"]},{"name":"ramp_right","tags":["arrow","arrows","direction","directions","maps","navigation","path","ramp","right","route","sign","traffic"]},{"name":"video_chat","tags":["bubble","cam","camera","chat","comment","communicate","facetime","feedback","message","speech","video","voice"]},{"name":"type_specimen","tags":[]},{"name":"man_4","tags":["abstract","boy","gender","male","man","social","symbol"]},{"name":"fluorescent","tags":["bright","fluorescent","lamp","light","lightbulb"]},{"name":"man_3","tags":["abstract","boy","gender","male","man","social","symbol"]},{"name":"fire_hydrant_alt","tags":[]},{"name":"macro_off","tags":["camera","disabled","enabled","flower","garden","image","macro","off","offline","on","slash"]},{"name":"mdi:ab-testing","tags":["developer / languages"]},{"name":"mdi:abacus","tags":["math"]},{"name":"mdi:abjad-arabic","tags":["alpha / numeric","writing system arabic"]},{"name":"mdi:abjad-hebrew","tags":["alpha / numeric","writing system hebrew"]},{"name":"mdi:abugida-devanagari","tags":["alpha / numeric","writing system devanagari"]},{"name":"mdi:abugida-thai","tags":["alpha / numeric","writing system thai"]},{"name":"mdi:access-point","tags":["wireless"]},{"name":"mdi:access-point-check","tags":["access point success","access point tick"]},{"name":"mdi:access-point-minus","tags":[]},{"name":"mdi:access-point-network","tags":[]},{"name":"mdi:access-point-network-off","tags":[]},{"name":"mdi:access-point-off","tags":[]},{"name":"mdi:access-point-plus","tags":[]},{"name":"mdi:access-point-remove","tags":[]},{"name":"mdi:account-alert","tags":["account / user","alert / error","user alert","account warning","user warning","person alert","person warning"]},{"name":"mdi:account-alert-outline","tags":["account / user","alert / error","user alert outline","account warning outline","user warning outline","person warning outline","person alert outline"]},{"name":"mdi:account-arrow-down","tags":["account / user","account download"]},{"name":"mdi:account-arrow-down-outline","tags":["account / user","account download outline"]},{"name":"mdi:account-arrow-left","tags":["account / user","user arrow left","person arrow left"]},{"name":"mdi:account-arrow-left-outline","tags":["account / user","user arrow left outline","person arrow left outline"]},{"name":"mdi:account-arrow-right","tags":["account / user","user arrow right","person arrow right"]},{"name":"mdi:account-arrow-right-outline","tags":["account / user","user arrow right outline","person arrow right outline"]},{"name":"mdi:account-arrow-up","tags":["account / user","account upload"]},{"name":"mdi:account-arrow-up-outline","tags":["account / user","account upload outline"]},{"name":"mdi:account-badge","tags":["account / user","account online","user online"]},{"name":"mdi:account-badge-outline","tags":["account / user","user online outline","account online outline"]},{"name":"mdi:account-box-multiple-outline","tags":["account / user"]},{"name":"mdi:account-cancel","tags":["account / user","user cancel","user block","person cancel","person block"]},{"name":"mdi:account-cancel-outline","tags":["account / user","user cancel outline","user block outline","person cancel outline","person block outline"]},{"name":"mdi:account-card","tags":["account / user"]},{"name":"mdi:account-card-outline","tags":["account / user"]},{"name":"mdi:account-cash","tags":["account / user","banking","currency"]},{"name":"mdi:account-cash-outline","tags":["account / user","banking","currency"]},{"name":"mdi:account-child-outline","tags":["account / user"]},{"name":"mdi:account-clock","tags":["account / user","date / time","user clock","account pending","person clock"]},{"name":"mdi:account-clock-outline","tags":["account / user","date / time","user clock outline","account pending outline","person clock outline"]},{"name":"mdi:account-cog","tags":["account / user","settings","account settings"]},{"name":"mdi:account-cog-outline","tags":["account / user","settings","account settings outline"]},{"name":"mdi:account-convert","tags":["account / user","user convert","person convert"]},{"name":"mdi:account-convert-outline","tags":["account / user"]},{"name":"mdi:account-cowboy-hat","tags":["account / user","agriculture","rancher"]},{"name":"mdi:account-cowboy-hat-outline","tags":["account / user","agriculture","rancher outline"]},{"name":"mdi:account-credit-card","tags":["account / user","banking","account payment","cardholder"]},{"name":"mdi:account-credit-card-outline","tags":["account / user","banking","account payment outline","cardholder outline"]},{"name":"mdi:account-details-outline","tags":["account / user","settings","person details outline","user details outline"]},{"name":"mdi:account-edit","tags":["account / user","edit / modify","user edit","person edit"]},{"name":"mdi:account-edit-outline","tags":["account / user","edit / modify"]},{"name":"mdi:account-eye","tags":["account / user","account view"]},{"name":"mdi:account-eye-outline","tags":["account / user","account view outline"]},{"name":"mdi:account-filter","tags":["account / user","account funnel","leads"]},{"name":"mdi:account-filter-outline","tags":["account / user","account funnel outline","leads outline"]},{"name":"mdi:account-group","tags":["account / user","home automation","user group","users group","person group","people group","accounts group"]},{"name":"mdi:account-group-outline","tags":["account / user","user group outline","users group outline","person group outline","people group outline","accounts group outline"]},{"name":"mdi:account-hard-hat","tags":["account / user","worker","construction"]},{"name":"mdi:account-hard-hat-outline","tags":["account / user","worker outline","construction outline"]},{"name":"mdi:account-heart","tags":["account / user","medical / hospital","user heart","person heart"]},{"name":"mdi:account-heart-outline","tags":["account / user","medical / hospital","user heart outline","person heart outline"]},{"name":"mdi:account-key","tags":["account / user","user key","person key"]},{"name":"mdi:account-key-outline","tags":["account / user","user key outline","person key outline"]},{"name":"mdi:account-lock","tags":["account / user","lock","account security","account secure","user lock","person lock"]},{"name":"mdi:account-lock-open","tags":["account / user","lock","account unlocked","user unlocked","user lock open"]},{"name":"mdi:account-lock-open-outline","tags":["account / user","lock","user lock open outline","user unlocked outline","account unlocked outline"]},{"name":"mdi:account-lock-outline","tags":["account / user","lock","account security outline","account secure outline","person lock outline","user lock outline"]},{"name":"mdi:account-minus","tags":["account / user","user minus","person minus"]},{"name":"mdi:account-minus-outline","tags":["account / user","user minus outline","person minus outline"]},{"name":"mdi:account-multiple-check","tags":["account / user","user multiple check","account multiple tick","accounts check","accounts tick","users check","users tick","user multiple tick","person multiple check","person multiple tick","people check","people tick","account multiple success"]},{"name":"mdi:account-multiple-check-outline","tags":["account / user","user multiple check outline","account multiple tick outline","accounts check outline","accounts tick outline","users check outline","users tick outline","user multiple tick outline","person multiple check outline","person multiple tick outline","people check outline","people tick outline","account multiple success outline"]},{"name":"mdi:account-multiple-remove","tags":["account / user","user multiple remove","person multiple remove"]},{"name":"mdi:account-multiple-remove-outline","tags":["account / user","user multiple remove outline","person multiple remove outline"]},{"name":"mdi:account-music-outline","tags":["account / user","artist outline"]},{"name":"mdi:account-network","tags":["account / user","user network","person network"]},{"name":"mdi:account-network-off","tags":["account / user"]},{"name":"mdi:account-network-off-outline","tags":["account / user"]},{"name":"mdi:account-network-outline","tags":["account / user","user network outline","person network outline"]},{"name":"mdi:account-off","tags":["account / user","user off","person off"]},{"name":"mdi:account-off-outline","tags":["account / user","user off outline","person off outline"]},{"name":"mdi:account-plus-outline","tags":["account / user","person add outline","register outline","user plus outline","account add outline","person plus outline","user add outline","invite"]},{"name":"mdi:account-question","tags":["account / user","user help","account question mark","account help","user question","person question","person help"]},{"name":"mdi:account-question-outline","tags":["account / user","account question mark outline","user help outline","account help outline","user question outline","person question outline","person help outline"]},{"name":"mdi:account-reactivate","tags":["account / user"]},{"name":"mdi:account-reactivate-outline","tags":["account / user"]},{"name":"mdi:account-remove","tags":["account / user","user remove","person remove"]},{"name":"mdi:account-remove-outline","tags":["account / user","user remove outline","person remove outline"]},{"name":"mdi:account-school","tags":["account / user","account student","account graduation"]},{"name":"mdi:account-school-outline","tags":["account / user","account student outline","account graduation outline"]},{"name":"mdi:account-search","tags":["account / user","user search","person search"]},{"name":"mdi:account-search-outline","tags":["account / user","user search outline","person search outline"]},{"name":"mdi:account-settings","tags":["account / user","settings","user settings","person settings"]},{"name":"mdi:account-settings-outline","tags":["account / user","settings"]},{"name":"mdi:account-star","tags":["account / user","user star","person star","account favorite"]},{"name":"mdi:account-star-outline","tags":["account / user","user star outline","person star outline"]},{"name":"mdi:account-supervisor-outline","tags":["account / user"]},{"name":"mdi:account-switch","tags":["account / user","user switch","accounts switch","users switch","person switch","people switch"]},{"name":"mdi:account-switch-outline","tags":["account / user"]},{"name":"mdi:account-sync","tags":["account / user","account cache"]},{"name":"mdi:account-sync-outline","tags":["account / user","account cache outline"]},{"name":"mdi:account-tag","tags":["account / user"]},{"name":"mdi:account-tag-outline","tags":["account / user"]},{"name":"mdi:account-tie","tags":["account / user","people / family","person tie","user tie"]},{"name":"mdi:account-tie-hat","tags":["account / user","transportation + flying","account pilot"]},{"name":"mdi:account-tie-hat-outline","tags":["account / user","transportation + flying","account pilot outline"]},{"name":"mdi:account-tie-outline","tags":["account / user"]},{"name":"mdi:account-tie-voice","tags":["account / user"]},{"name":"mdi:account-tie-voice-off","tags":["account / user"]},{"name":"mdi:account-tie-voice-off-outline","tags":["account / user"]},{"name":"mdi:account-tie-voice-outline","tags":["account / user"]},{"name":"mdi:account-tie-woman","tags":["account / user","people / family","business woman"]},{"name":"mdi:account-wrench","tags":["account / user","account service"]},{"name":"mdi:account-wrench-outline","tags":["account / user","account service outline"]},{"name":"mdi:advertisements","tags":["ads"]},{"name":"mdi:advertisements-off","tags":["ads off"]},{"name":"mdi:air-conditioner","tags":["home automation","automotive","ac unit"]},{"name":"mdi:air-filter","tags":["home automation","water filter","filter"]},{"name":"mdi:air-horn","tags":[]},{"name":"mdi:air-humidifier","tags":["home automation"]},{"name":"mdi:air-humidifier-off","tags":["home automation","air dehumidifier"]},{"name":"mdi:air-purifier-off","tags":["home automation"]},{"name":"mdi:airbag","tags":["automotive"]},{"name":"mdi:airballoon","tags":["transportation + other","transportation + flying","hot air balloon"]},{"name":"mdi:airballoon-outline","tags":["transportation + flying","hot air balloon outline"]},{"name":"mdi:airplane","tags":["transportation + flying","navigation","aeroplane","airplanemode active","flight","local airport","flight mode","plane"]},{"name":"mdi:airplane-alert","tags":["transportation + flying","alert / error"]},{"name":"mdi:airplane-check","tags":["transportation + flying","airplace success","airplane tick"]},{"name":"mdi:airplane-clock","tags":["transportation + flying","date / time","airplane schedule","airplane time","airplane date"]},{"name":"mdi:airplane-cog","tags":["transportation + flying","settings","airplane settings"]},{"name":"mdi:airplane-edit","tags":["transportation + flying","edit / modify"]},{"name":"mdi:airplane-marker","tags":["transportation + flying","navigation","airplane location","airplane gps"]},{"name":"mdi:airplane-minus","tags":["transportation + flying"]},{"name":"mdi:airplane-off","tags":["transportation + flying","aeroplane off","airplanemode inactive","flight mode off","plane off"]},{"name":"mdi:airplane-plus","tags":["transportation + flying"]},{"name":"mdi:airplane-remove","tags":["transportation + flying"]},{"name":"mdi:airplane-search","tags":["transportation + flying","airplane find"]},{"name":"mdi:airplane-settings","tags":["transportation + flying","settings"]},{"name":"mdi:airport","tags":["places","transportation + flying"]},{"name":"mdi:alarm-bell","tags":["notification"]},{"name":"mdi:alarm-light","tags":["home automation"]},{"name":"mdi:alarm-light-off","tags":["home automation"]},{"name":"mdi:alarm-light-off-outline","tags":["home automation"]},{"name":"mdi:alarm-light-outline","tags":["home automation"]},{"name":"mdi:alarm-multiple","tags":["date / time","alarms","alarm clock multiple","alarm clocks"]},{"name":"mdi:alarm-note","tags":[]},{"name":"mdi:alarm-note-off","tags":[]},{"name":"mdi:alarm-panel","tags":["home automation"]},{"name":"mdi:alarm-panel-outline","tags":["home automation"]},{"name":"mdi:alert-box","tags":["alert / error","warning box"]},{"name":"mdi:alert-box-outline","tags":["alert / error","warning box outline"]},{"name":"mdi:alert-circle-check","tags":["alert / error","alert circle success"]},{"name":"mdi:alert-circle-check-outline","tags":["alert / error","alert circle success outline"]},{"name":"mdi:alert-decagram-outline","tags":["alert / error","warning decagram outline"]},{"name":"mdi:alert-minus","tags":["alert / error"]},{"name":"mdi:alert-minus-outline","tags":["alert / error"]},{"name":"mdi:alert-octagon-outline","tags":["alert / error","warning octagon outline","stop alert outline"]},{"name":"mdi:alert-octagram","tags":["alert / error","warning octagram"]},{"name":"mdi:alert-octagram-outline","tags":["alert / error","warning octagram outline"]},{"name":"mdi:alert-outline","tags":["alert / error","warning outline"]},{"name":"mdi:alert-plus","tags":["alert / error"]},{"name":"mdi:alert-plus-outline","tags":["alert / error"]},{"name":"mdi:alert-remove","tags":["alert / error"]},{"name":"mdi:alert-remove-outline","tags":["alert / error"]},{"name":"mdi:alert-rhombus","tags":["alert / error"]},{"name":"mdi:alert-rhombus-outline","tags":["alert / error"]},{"name":"mdi:alien","tags":[]},{"name":"mdi:alien-outline","tags":[]},{"name":"mdi:all-inclusive-box","tags":["infinity box","forever box"]},{"name":"mdi:all-inclusive-box-outline","tags":["forever box outline","infinity box outline"]},{"name":"mdi:allergy","tags":["medical / hospital","hand","rash","germ"]},{"name":"mdi:alpha","tags":["alpha / numeric"]},{"name":"mdi:alpha-a","tags":["alpha / numeric","alphabet a","letter a"]},{"name":"mdi:alpha-a-box","tags":["alpha / numeric","alphabet a box","letter a box"]},{"name":"mdi:alpha-a-box-outline","tags":["alpha / numeric","alphabet a box outline","letter a box outline"]},{"name":"mdi:alpha-a-circle","tags":["alpha / numeric","alphabet a circle","letter a circle"]},{"name":"mdi:alpha-a-circle-outline","tags":["alpha / numeric","alphabet a circle outline","letter a circle outline"]},{"name":"mdi:alpha-b","tags":["alpha / numeric","alphabet b","letter b"]},{"name":"mdi:alpha-b-box","tags":["alpha / numeric","alphabet b box","letter b box"]},{"name":"mdi:alpha-b-box-outline","tags":["alpha / numeric","alphabet b box outline","letter b box outline"]},{"name":"mdi:alpha-b-circle","tags":["alpha / numeric","alphabet b circle","letter b circle"]},{"name":"mdi:alpha-b-circle-outline","tags":["alpha / numeric","alphabet b circle outline","letter b circle outline"]},{"name":"mdi:alpha-c","tags":["alpha / numeric","alphabet c","letter c"]},{"name":"mdi:alpha-c-box","tags":["alpha / numeric","alphabet c box","letter c box"]},{"name":"mdi:alpha-c-box-outline","tags":["alpha / numeric","alphabet c box outline","letter c box outline"]},{"name":"mdi:alpha-c-circle","tags":["alpha / numeric","alphabet c circle","letter c circle"]},{"name":"mdi:alpha-c-circle-outline","tags":["alpha / numeric","alphabet c circle outline","letter c circle outline"]},{"name":"mdi:alpha-d","tags":["automotive","alpha / numeric","alphabet d","letter d","drive"]},{"name":"mdi:alpha-d-box","tags":["alpha / numeric","alphabet d box","letter d box"]},{"name":"mdi:alpha-d-box-outline","tags":["alpha / numeric","alphabet d box outline","letter d box outline"]},{"name":"mdi:alpha-d-circle","tags":["alpha / numeric","alphabet d circle","letter d circle"]},{"name":"mdi:alpha-d-circle-outline","tags":["alpha / numeric","alphabet d circle outline","letter d circle outline"]},{"name":"mdi:alpha-e","tags":["alpha / numeric","alphabet e","letter e"]},{"name":"mdi:alpha-e-box","tags":["alpha / numeric","alphabet e box","letter e box"]},{"name":"mdi:alpha-e-box-outline","tags":["alpha / numeric","alphabet e box outline","letter e box outline"]},{"name":"mdi:alpha-e-circle","tags":["alpha / numeric","alphabet e circle","letter e circle"]},{"name":"mdi:alpha-e-circle-outline","tags":["alpha / numeric","alphabet e circle outline","letter e circle outline"]},{"name":"mdi:alpha-f","tags":["alpha / numeric","alphabet f","letter f"]},{"name":"mdi:alpha-f-box","tags":["alpha / numeric","alphabet f box","letter f box"]},{"name":"mdi:alpha-f-box-outline","tags":["alpha / numeric","alphabet f box outline","letter f box outline"]},{"name":"mdi:alpha-f-circle","tags":["alpha / numeric","alphabet f circle","letter f circle"]},{"name":"mdi:alpha-f-circle-outline","tags":["alpha / numeric","alphabet f circle outline","letter f circle outline"]},{"name":"mdi:alpha-g","tags":["alpha / numeric","alphabet g","letter g"]},{"name":"mdi:alpha-g-box","tags":["alpha / numeric","alphabet g box","letter g box"]},{"name":"mdi:alpha-g-box-outline","tags":["alpha / numeric","alphabet g box outline","letter g box outline"]},{"name":"mdi:alpha-g-circle","tags":["alpha / numeric","alphabet g circle","letter g circle"]},{"name":"mdi:alpha-g-circle-outline","tags":["alpha / numeric","alphabet g circle outline","letter g circle outline"]},{"name":"mdi:alpha-h","tags":["alpha / numeric","alphabet h","letter h"]},{"name":"mdi:alpha-h-box","tags":["alpha / numeric","alphabet h box","letter h box"]},{"name":"mdi:alpha-h-box-outline","tags":["alpha / numeric","alphabet h box outline","letter h box outline"]},{"name":"mdi:alpha-h-circle","tags":["alpha / numeric","alphabet h circle","letter h circle"]},{"name":"mdi:alpha-h-circle-outline","tags":["alpha / numeric","alphabet h circle outline","letter h circle outline","helipad"]},{"name":"mdi:alpha-i","tags":["alpha / numeric","alphabet i","letter i","roman numeral 1"]},{"name":"mdi:alpha-i-box","tags":["alpha / numeric","alphabet i box","letter i box"]},{"name":"mdi:alpha-i-box-outline","tags":["alpha / numeric","alphabet i box outline","letter i box outline"]},{"name":"mdi:alpha-i-circle","tags":["alpha / numeric","alphabet i circle","letter i circle"]},{"name":"mdi:alpha-i-circle-outline","tags":["alpha / numeric","alphabet i circle outline","letter i circle outline"]},{"name":"mdi:alpha-j","tags":["alpha / numeric","alphabet j","letter j"]},{"name":"mdi:alpha-j-box","tags":["alpha / numeric","alphabet j box","letter j box"]},{"name":"mdi:alpha-j-box-outline","tags":["alpha / numeric","alphabet j box outline","letter j box outline"]},{"name":"mdi:alpha-j-circle","tags":["alpha / numeric","alphabet j circle","letter j circle"]},{"name":"mdi:alpha-j-circle-outline","tags":["alpha / numeric","alphabet j circle outline","letter j circle outline"]},{"name":"mdi:alpha-k","tags":["alpha / numeric","alphabet k","letter k"]},{"name":"mdi:alpha-k-box","tags":["alpha / numeric","alphabet k box","letter k box"]},{"name":"mdi:alpha-k-box-outline","tags":["alpha / numeric","alphabet k box outline","letter k box outline"]},{"name":"mdi:alpha-k-circle","tags":["alpha / numeric","alphabet k circle","letter k circle"]},{"name":"mdi:alpha-k-circle-outline","tags":["alpha / numeric","alphabet k circle outline","letter k circle outline"]},{"name":"mdi:alpha-l","tags":["alpha / numeric","alphabet l","letter l"]},{"name":"mdi:alpha-l-box","tags":["alpha / numeric","alphabet l box","letter l box"]},{"name":"mdi:alpha-l-box-outline","tags":["alpha / numeric","alphabet l box outline","letter l box outline"]},{"name":"mdi:alpha-l-circle","tags":["alpha / numeric","alphabet l circle","letter l circle"]},{"name":"mdi:alpha-l-circle-outline","tags":["alpha / numeric","alphabet l circle outline","letter l circle outline"]},{"name":"mdi:alpha-m","tags":["alpha / numeric","alphabet m","letter m"]},{"name":"mdi:alpha-m-box","tags":["alpha / numeric","alphabet m box","letter m box"]},{"name":"mdi:alpha-m-box-outline","tags":["alpha / numeric","alphabet m box outline","letter m box outline"]},{"name":"mdi:alpha-m-circle","tags":["alpha / numeric","alphabet m circle","letter m circle"]},{"name":"mdi:alpha-m-circle-outline","tags":["alpha / numeric","alphabet m circle outline","letter m circle outline"]},{"name":"mdi:alpha-n","tags":["automotive","alpha / numeric","alphabet n","letter n","neutral"]},{"name":"mdi:alpha-n-box","tags":["alpha / numeric","alphabet n box","letter n box"]},{"name":"mdi:alpha-n-box-outline","tags":["alpha / numeric","alphabet n box outline","letter n box outline"]},{"name":"mdi:alpha-n-circle","tags":["alpha / numeric","alphabet n circle","letter n circle"]},{"name":"mdi:alpha-n-circle-outline","tags":["alpha / numeric","alphabet n circle outline","letter n circle outline"]},{"name":"mdi:alpha-o","tags":["alpha / numeric","alphabet o","letter o"]},{"name":"mdi:alpha-o-box","tags":["alpha / numeric","alphabet o box","letter o box"]},{"name":"mdi:alpha-o-box-outline","tags":["alpha / numeric","alphabet o box outline","letter o box outline"]},{"name":"mdi:alpha-o-circle","tags":["alpha / numeric","alphabet o circle","letter o circle"]},{"name":"mdi:alpha-o-circle-outline","tags":["alpha / numeric","alphabet o circle outline","letter o circle outline"]},{"name":"mdi:alpha-p","tags":["automotive","alpha / numeric","alphabet p","letter p","park"]},{"name":"mdi:alpha-p-box","tags":["alpha / numeric","alphabet p box","letter p box"]},{"name":"mdi:alpha-p-box-outline","tags":["alpha / numeric","alphabet p box outline","letter p box outline"]},{"name":"mdi:alpha-p-circle","tags":["alpha / numeric","alphabet p circle","letter p circle"]},{"name":"mdi:alpha-p-circle-outline","tags":["alpha / numeric","alphabet p circle outline","letter p circle outline"]},{"name":"mdi:alpha-q","tags":["alpha / numeric","alphabet q","letter q"]},{"name":"mdi:alpha-q-box","tags":["alpha / numeric","alphabet q box","letter q box"]},{"name":"mdi:alpha-q-box-outline","tags":["alpha / numeric","alphabet q box outline","letter q box outline"]},{"name":"mdi:alpha-q-circle","tags":["alpha / numeric","alphabet q circle","letter q circle"]},{"name":"mdi:alpha-q-circle-outline","tags":["alpha / numeric","alphabet q circle outline","letter q circle outline"]},{"name":"mdi:alpha-r","tags":["automotive","alpha / numeric","alphabet r","letter r","reverse"]},{"name":"mdi:alpha-r-box","tags":["alpha / numeric","alphabet r box","letter r box"]},{"name":"mdi:alpha-r-box-outline","tags":["alpha / numeric","alphabet r box outline","letter r box outline"]},{"name":"mdi:alpha-r-circle","tags":["alpha / numeric","alphabet r circle","letter r circle"]},{"name":"mdi:alpha-r-circle-outline","tags":["alpha / numeric","alphabet r circle outline","letter r circle outline"]},{"name":"mdi:alpha-s","tags":["alpha / numeric","alphabet s","letter s"]},{"name":"mdi:alpha-s-box","tags":["alpha / numeric","alphabet s box","letter s box"]},{"name":"mdi:alpha-s-box-outline","tags":["alpha / numeric","alphabet s box outline","letter s box outline"]},{"name":"mdi:alpha-s-circle","tags":["alpha / numeric","alphabet s circle","letter s circle"]},{"name":"mdi:alpha-s-circle-outline","tags":["alpha / numeric","alphabet s circle outline","letter s circle outline"]},{"name":"mdi:alpha-t","tags":["alpha / numeric","alphabet t","letter t"]},{"name":"mdi:alpha-t-box","tags":["alpha / numeric","alphabet t box","letter t box"]},{"name":"mdi:alpha-t-box-outline","tags":["alpha / numeric","alphabet t box outline","letter t box outline"]},{"name":"mdi:alpha-t-circle","tags":["alpha / numeric","alphabet t circle","letter t circle"]},{"name":"mdi:alpha-t-circle-outline","tags":["alpha / numeric","alphabet t circle outline","letter t circle outline"]},{"name":"mdi:alpha-u","tags":["alpha / numeric","alphabet u","letter u"]},{"name":"mdi:alpha-u-box","tags":["alpha / numeric","alphabet u box","letter u box"]},{"name":"mdi:alpha-u-box-outline","tags":["alpha / numeric","alphabet u box outline","letter u box outline"]},{"name":"mdi:alpha-u-circle","tags":["alpha / numeric","alphabet u circle","letter u circle"]},{"name":"mdi:alpha-u-circle-outline","tags":["alpha / numeric","alphabet u circle outline","letter u circle outline"]},{"name":"mdi:alpha-v","tags":["alpha / numeric","alphabet v","letter v","roman numeral 5"]},{"name":"mdi:alpha-v-box","tags":["alpha / numeric","alphabet v box","letter v box"]},{"name":"mdi:alpha-v-box-outline","tags":["alpha / numeric","alphabet v box outline","letter v box outline"]},{"name":"mdi:alpha-v-circle","tags":["alpha / numeric","alphabet v circle","letter v circle"]},{"name":"mdi:alpha-v-circle-outline","tags":["alpha / numeric","alphabet v circle outline","letter v circle outline"]},{"name":"mdi:alpha-w","tags":["alpha / numeric","alphabet w","letter w"]},{"name":"mdi:alpha-w-box","tags":["alpha / numeric","alphabet w box","letter w box"]},{"name":"mdi:alpha-w-box-outline","tags":["alpha / numeric","alphabet w box outline","letter w box outline"]},{"name":"mdi:alpha-w-circle","tags":["alpha / numeric","alphabet w circle","letter w circle"]},{"name":"mdi:alpha-w-circle-outline","tags":["alpha / numeric","alphabet w circle outline","letter w circle outline"]},{"name":"mdi:alpha-x","tags":["alpha / numeric","alphabet x","letter x","roman numeral 10"]},{"name":"mdi:alpha-x-box","tags":["alpha / numeric","alphabet x box","letter x box"]},{"name":"mdi:alpha-x-box-outline","tags":["alpha / numeric","alphabet x box outline","letter x box outline"]},{"name":"mdi:alpha-x-circle","tags":["alpha / numeric","alphabet x circle","letter x circle"]},{"name":"mdi:alpha-x-circle-outline","tags":["alpha / numeric","alphabet x circle outline","letter x circle outline"]},{"name":"mdi:alpha-y","tags":["alpha / numeric","alphabet y","letter y"]},{"name":"mdi:alpha-y-box","tags":["alpha / numeric","alphabet y box","letter y box"]},{"name":"mdi:alpha-y-box-outline","tags":["alpha / numeric","alphabet y box outline","letter y box outline"]},{"name":"mdi:alpha-y-circle","tags":["alpha / numeric","alphabet y circle","letter y circle"]},{"name":"mdi:alpha-y-circle-outline","tags":["alpha / numeric","alphabet y circle outline","letter y circle outline"]},{"name":"mdi:alpha-z","tags":["alpha / numeric","alphabet z","letter z"]},{"name":"mdi:alpha-z-box","tags":["alpha / numeric","alphabet z box","letter z box"]},{"name":"mdi:alpha-z-box-outline","tags":["alpha / numeric","alphabet z box outline","letter z box outline"]},{"name":"mdi:alpha-z-circle","tags":["alpha / numeric","alphabet z circle","letter z circle"]},{"name":"mdi:alpha-z-circle-outline","tags":["alpha / numeric","alphabet z circle outline","letter z circle outline"]},{"name":"mdi:alphabet-aurebesh","tags":["alpha / numeric","writing system aurebesh"]},{"name":"mdi:alphabet-cyrillic","tags":["alpha / numeric","writing system cyrillic"]},{"name":"mdi:alphabet-greek","tags":["alpha / numeric","writing system greek"]},{"name":"mdi:alphabet-latin","tags":["alpha / numeric","writing system latin"]},{"name":"mdi:alphabet-piqad","tags":["alpha / numeric","writing system piqad"]},{"name":"mdi:alphabet-tengwar","tags":["alpha / numeric","writing system tengwar"]},{"name":"mdi:alphabetical","tags":["alpha / numeric","letters","a b c","abc"]},{"name":"mdi:alphabetical-off","tags":["alpha / numeric","letters off","abc off","a b c off"]},{"name":"mdi:alphabetical-variant","tags":["alpha / numeric","letters","abc","a b c"]},{"name":"mdi:alphabetical-variant-off","tags":["alpha / numeric","letters off","abc off","a b c off"]},{"name":"mdi:altimeter","tags":[]},{"name":"mdi:ambulance","tags":["transportation + road","medical / hospital"]},{"name":"mdi:ammunition","tags":["bullets"]},{"name":"mdi:ampersand","tags":["and"]},{"name":"mdi:amplifier","tags":["home automation","music"]},{"name":"mdi:amplifier-off","tags":[]},{"name":"mdi:angle-acute","tags":["math"]},{"name":"mdi:angle-obtuse","tags":["math"]},{"name":"mdi:angle-right","tags":["math"]},{"name":"mdi:animation-outline","tags":[]},{"name":"mdi:animation-play-outline","tags":[]},{"name":"mdi:anvil","tags":[]},{"name":"mdi:api-off","tags":["developer / languages"]},{"name":"mdi:apple-keyboard-caps","tags":[]},{"name":"mdi:apple-keyboard-command","tags":[]},{"name":"mdi:apple-keyboard-control","tags":[]},{"name":"mdi:apple-keyboard-option","tags":[]},{"name":"mdi:apple-keyboard-shift","tags":[]},{"name":"mdi:application","tags":["iframe"]},{"name":"mdi:application-array","tags":["developer / languages","iframe array"]},{"name":"mdi:application-array-outline","tags":["developer / languages","iframe array outline"]},{"name":"mdi:application-braces","tags":["developer / languages","iframe braces"]},{"name":"mdi:application-braces-outline","tags":["developer / languages","iframe braces outline"]},{"name":"mdi:application-brackets","tags":["developer / languages","iframe brackets"]},{"name":"mdi:application-brackets-outline","tags":["developer / languages","iframe brackets outline"]},{"name":"mdi:application-cog","tags":["settings","iframe cog"]},{"name":"mdi:application-cog-outline","tags":["settings","application settings","iframe cog outline"]},{"name":"mdi:application-edit","tags":["edit / modify","iframe edit"]},{"name":"mdi:application-edit-outline","tags":["edit / modify","iframe edit outline"]},{"name":"mdi:application-export","tags":["iframe export outline"]},{"name":"mdi:application-import","tags":["iframe import outline"]},{"name":"mdi:application-outline","tags":["web asset","iframe outline"]},{"name":"mdi:application-parentheses","tags":["developer / languages","iframe parentheses"]},{"name":"mdi:application-parentheses-outline","tags":["developer / languages","iframe parentheses outline"]},{"name":"mdi:application-settings","tags":["settings","iframe settings"]},{"name":"mdi:application-settings-outline","tags":["settings","iframe settings outline"]},{"name":"mdi:application-variable","tags":["developer / languages","iframe variable"]},{"name":"mdi:application-variable-outline","tags":["developer / languages","iframe variable outline"]},{"name":"mdi:approximately-equal","tags":["math"]},{"name":"mdi:approximately-equal-box","tags":["math"]},{"name":"mdi:archive","tags":["box"]},{"name":"mdi:archive-alert","tags":["alert / error","box alert"]},{"name":"mdi:archive-alert-outline","tags":["alert / error","box alert outline"]},{"name":"mdi:archive-arrow-down","tags":["box arrow down","this side down"]},{"name":"mdi:archive-arrow-down-outline","tags":["box arrow down","this side down outline"]},{"name":"mdi:archive-arrow-up","tags":["box arrow up","this side up"]},{"name":"mdi:archive-arrow-up-outline","tags":["box arrow up outline","this side up outline"]},{"name":"mdi:archive-cancel","tags":["box cancel"]},{"name":"mdi:archive-cancel-outline","tags":["box cancel outline"]},{"name":"mdi:archive-check","tags":["box check","archive success","box success"]},{"name":"mdi:archive-check-outline","tags":["box check outline","archive success outline","box success outline"]},{"name":"mdi:archive-clock","tags":["date / time","box clock","box time","archive time"]},{"name":"mdi:archive-clock-outline","tags":["date / time","box clock outline","box time outline","archive time outline"]},{"name":"mdi:archive-cog","tags":["settings","box cog"]},{"name":"mdi:archive-cog-outline","tags":["settings","box cog outline"]},{"name":"mdi:archive-edit","tags":["edit / modify","box edit"]},{"name":"mdi:archive-edit-outline","tags":["edit / modify","box edit outline"]},{"name":"mdi:archive-eye","tags":["archive view","box eye","box view"]},{"name":"mdi:archive-eye-outline","tags":["archive view outline","box eye outline","box view outline"]},{"name":"mdi:archive-lock","tags":["lock","box lock"]},{"name":"mdi:archive-lock-open","tags":["lock","box lock open"]},{"name":"mdi:archive-lock-open-outline","tags":["lock","box lock open outline"]},{"name":"mdi:archive-lock-outline","tags":["lock","box lock outline"]},{"name":"mdi:archive-marker","tags":["navigation","archive location","box marker","box location"]},{"name":"mdi:archive-marker-outline","tags":["navigation","archive location outline","box marker outline","box location outline"]},{"name":"mdi:archive-minus","tags":["box minus"]},{"name":"mdi:archive-minus-outline","tags":["box minus outline"]},{"name":"mdi:archive-music","tags":["music","box music"]},{"name":"mdi:archive-music-outline","tags":["music","box music outline"]},{"name":"mdi:archive-off","tags":["box off"]},{"name":"mdi:archive-off-outline","tags":["box off outline"]},{"name":"mdi:archive-outline","tags":["box outline"]},{"name":"mdi:archive-plus","tags":["archive add","box plus","box add"]},{"name":"mdi:archive-plus-outline","tags":["archive add outline","box plus outline","box add outline"]},{"name":"mdi:archive-refresh","tags":["box refresh"]},{"name":"mdi:archive-refresh-outline","tags":["box refresh outline"]},{"name":"mdi:archive-remove","tags":["box remove"]},{"name":"mdi:archive-remove-outline","tags":["box remove outline"]},{"name":"mdi:archive-search","tags":["box search"]},{"name":"mdi:archive-search-outline","tags":["box search outline"]},{"name":"mdi:archive-settings","tags":["settings","box settings"]},{"name":"mdi:archive-settings-outline","tags":["settings","box settings outline"]},{"name":"mdi:archive-star","tags":["archive favorite","box star","box favorite"]},{"name":"mdi:archive-star-outline","tags":["archive favorite outline","box star outline","box favorite outline"]},{"name":"mdi:archive-sync","tags":["box sync"]},{"name":"mdi:archive-sync-outline","tags":["box sync outline"]},{"name":"mdi:arm-flex","tags":[]},{"name":"mdi:arm-flex-outline","tags":[]},{"name":"mdi:arrange-bring-forward","tags":["arrange","geographic information system"]},{"name":"mdi:arrange-bring-to-front","tags":["arrange","geographic information system"]},{"name":"mdi:arrange-send-backward","tags":["arrange","geographic information system"]},{"name":"mdi:arrange-send-to-back","tags":["arrange","geographic information system"]},{"name":"mdi:arrow-all","tags":["arrow"]},{"name":"mdi:arrow-bottom-left","tags":["arrow","arrow down left"]},{"name":"mdi:arrow-bottom-left-bold-box","tags":["arrow"]},{"name":"mdi:arrow-bottom-left-bold-box-outline","tags":["arrow"]},{"name":"mdi:arrow-bottom-left-bold-outline","tags":["arrow","arrow down left bold outline"]},{"name":"mdi:arrow-bottom-left-thick","tags":["arrow","arrow down left thick","arrow bottom left bold","arrow down left bold"]},{"name":"mdi:arrow-bottom-left-thin","tags":["arrow"]},{"name":"mdi:arrow-bottom-right","tags":["arrow","arrow down right"]},{"name":"mdi:arrow-bottom-right-bold-box","tags":["arrow"]},{"name":"mdi:arrow-bottom-right-bold-box-outline","tags":["arrow"]},{"name":"mdi:arrow-bottom-right-bold-outline","tags":["arrow","arrow down right bold outline"]},{"name":"mdi:arrow-bottom-right-thick","tags":["arrow","arrow down right thick","arrow bottom right bold","arrow down right bold"]},{"name":"mdi:arrow-bottom-right-thin","tags":["arrow"]},{"name":"mdi:arrow-collapse","tags":["arrow","arrow compress"]},{"name":"mdi:arrow-collapse-all","tags":["arrow","arrow compress all"]},{"name":"mdi:arrow-collapse-down","tags":["arrow","arrow compress down"]},{"name":"mdi:arrow-collapse-left","tags":["arrow","arrow compress left"]},{"name":"mdi:arrow-collapse-right","tags":["arrow","arrow compress right"]},{"name":"mdi:arrow-collapse-up","tags":["arrow","arrow compress up"]},{"name":"mdi:arrow-decision","tags":["arrow","proxy"]},{"name":"mdi:arrow-decision-auto","tags":["proxy auto"]},{"name":"mdi:arrow-decision-auto-outline","tags":["proxy auto outline"]},{"name":"mdi:arrow-decision-outline","tags":["arrow","proxy outline"]},{"name":"mdi:arrow-down","tags":["arrow","arrow downward","arrow bottom"]},{"name":"mdi:arrow-down-bold","tags":["arrow","arrow bottom bold"]},{"name":"mdi:arrow-down-bold-box","tags":["arrow","arrow bottom bold box"]},{"name":"mdi:arrow-down-bold-box-outline","tags":["arrow","arrow bottom bold box outline"]},{"name":"mdi:arrow-down-bold-circle","tags":["arrow","arrow bottom bold circle"]},{"name":"mdi:arrow-down-bold-circle-outline","tags":["arrow","arrow bottom bold circle outline"]},{"name":"mdi:arrow-down-bold-hexagon-outline","tags":["arrow","arrow bottom bold hexagon outline"]},{"name":"mdi:arrow-down-bold-outline","tags":["arrow","arrow bottom bold outline"]},{"name":"mdi:arrow-down-box","tags":["arrow","arrow bottom box"]},{"name":"mdi:arrow-down-circle","tags":["arrow","arrow bottom circle"]},{"name":"mdi:arrow-down-circle-outline","tags":["arrow","arrow bottom circle outline"]},{"name":"mdi:arrow-down-drop-circle-outline","tags":["arrow","arrow bottom drop circle outline"]},{"name":"mdi:arrow-down-left","tags":["arrow"]},{"name":"mdi:arrow-down-left-bold","tags":["arrow"]},{"name":"mdi:arrow-down-right","tags":["arrow"]},{"name":"mdi:arrow-down-right-bold","tags":["arrow"]},{"name":"mdi:arrow-down-thick","tags":["arrow","arrow bottom thick","arrow down bold","arrow bottom bold"]},{"name":"mdi:arrow-down-thin","tags":["arrow"]},{"name":"mdi:arrow-expand","tags":["arrow"]},{"name":"mdi:arrow-expand-all","tags":["arrow","geographic information system"]},{"name":"mdi:arrow-expand-down","tags":["arrow"]},{"name":"mdi:arrow-expand-left","tags":["arrow"]},{"name":"mdi:arrow-expand-right","tags":["arrow"]},{"name":"mdi:arrow-expand-up","tags":["arrow"]},{"name":"mdi:arrow-horizontal-lock","tags":["lock","arrow","scroll horizontal lock"]},{"name":"mdi:arrow-left-bold","tags":["arrow","automotive"]},{"name":"mdi:arrow-left-bold-box","tags":["arrow"]},{"name":"mdi:arrow-left-bold-box-outline","tags":["arrow"]},{"name":"mdi:arrow-left-bold-circle","tags":["arrow"]},{"name":"mdi:arrow-left-bold-circle-outline","tags":["arrow"]},{"name":"mdi:arrow-left-bold-hexagon-outline","tags":["arrow"]},{"name":"mdi:arrow-left-bold-outline","tags":["arrow","automotive"]},{"name":"mdi:arrow-left-bottom","tags":[]},{"name":"mdi:arrow-left-bottom-bold","tags":[]},{"name":"mdi:arrow-left-box","tags":["arrow"]},{"name":"mdi:arrow-left-circle","tags":["arrow","arrow back circle"]},{"name":"mdi:arrow-left-circle-outline","tags":["arrow"]},{"name":"mdi:arrow-left-drop-circle","tags":["arrow"]},{"name":"mdi:arrow-left-drop-circle-outline","tags":["arrow"]},{"name":"mdi:arrow-left-right","tags":["arrow"]},{"name":"mdi:arrow-left-right-bold","tags":["arrow"]},{"name":"mdi:arrow-left-right-bold-outline","tags":["arrow"]},{"name":"mdi:arrow-left-thick","tags":["arrow","arrow left bold"]},{"name":"mdi:arrow-left-thin","tags":["arrow"]},{"name":"mdi:arrow-left-top","tags":["turn left"]},{"name":"mdi:arrow-left-top-bold","tags":["turn left bold"]},{"name":"mdi:arrow-projectile","tags":["gaming / rpg","sport"]},{"name":"mdi:arrow-projectile-multiple","tags":["gaming / rpg","sport"]},{"name":"mdi:arrow-right-bold","tags":["arrow","automotive"]},{"name":"mdi:arrow-right-bold-box","tags":["arrow"]},{"name":"mdi:arrow-right-bold-box-outline","tags":["arrow"]},{"name":"mdi:arrow-right-bold-circle","tags":["arrow"]},{"name":"mdi:arrow-right-bold-circle-outline","tags":["arrow"]},{"name":"mdi:arrow-right-bold-hexagon-outline","tags":["arrow"]},{"name":"mdi:arrow-right-bold-outline","tags":["arrow","automotive"]},{"name":"mdi:arrow-right-bottom","tags":[]},{"name":"mdi:arrow-right-bottom-bold","tags":[]},{"name":"mdi:arrow-right-box","tags":["arrow"]},{"name":"mdi:arrow-right-circle","tags":["arrow","arrow forward circle"]},{"name":"mdi:arrow-right-circle-outline","tags":["arrow"]},{"name":"mdi:arrow-right-drop-circle","tags":["arrow"]},{"name":"mdi:arrow-right-drop-circle-outline","tags":["arrow"]},{"name":"mdi:arrow-right-thick","tags":["arrow","arrow right bold"]},{"name":"mdi:arrow-right-thin","tags":["arrow"]},{"name":"mdi:arrow-right-top","tags":["turn right"]},{"name":"mdi:arrow-right-top-bold","tags":["turn right bold"]},{"name":"mdi:arrow-split-horizontal","tags":["arrow","resize vertical","resize"]},{"name":"mdi:arrow-split-vertical","tags":["arrow","resize horizontal","resize"]},{"name":"mdi:arrow-top-left","tags":["arrow","arrow up left"]},{"name":"mdi:arrow-top-left-bold-box","tags":["arrow"]},{"name":"mdi:arrow-top-left-bold-box-outline","tags":["arrow"]},{"name":"mdi:arrow-top-left-bold-outline","tags":["arrow","arrow up left bold outline"]},{"name":"mdi:arrow-top-left-bottom-right","tags":["arrow"]},{"name":"mdi:arrow-top-left-bottom-right-bold","tags":["arrow"]},{"name":"mdi:arrow-top-left-thick","tags":["arrow","arrow up left thick","arrow top left bold","arrow up left bold"]},{"name":"mdi:arrow-top-left-thin","tags":["arrow"]},{"name":"mdi:arrow-top-right","tags":["arrow","arrow up right"]},{"name":"mdi:arrow-top-right-bold-box","tags":["arrow"]},{"name":"mdi:arrow-top-right-bold-box-outline","tags":["arrow"]},{"name":"mdi:arrow-top-right-bold-outline","tags":["arrow","arrow up right bold outline"]},{"name":"mdi:arrow-top-right-bottom-left","tags":["arrow"]},{"name":"mdi:arrow-top-right-bottom-left-bold","tags":["arrow"]},{"name":"mdi:arrow-top-right-thick","tags":["arrow","arrow up right thick","arrow top right bold","arrow up right bold"]},{"name":"mdi:arrow-top-right-thin","tags":["arrow"]},{"name":"mdi:arrow-u-down-left","tags":["u turn left"]},{"name":"mdi:arrow-u-down-left-bold","tags":["u turn left bold"]},{"name":"mdi:arrow-u-down-right","tags":["u turn right"]},{"name":"mdi:arrow-u-down-right-bold","tags":["u turn right bold"]},{"name":"mdi:arrow-u-left-bottom","tags":["undo"]},{"name":"mdi:arrow-u-left-bottom-bold","tags":["undo"]},{"name":"mdi:arrow-u-left-top","tags":["undo"]},{"name":"mdi:arrow-u-left-top-bold","tags":["undo"]},{"name":"mdi:arrow-u-right-bottom","tags":["redo"]},{"name":"mdi:arrow-u-right-bottom-bold","tags":["redo"]},{"name":"mdi:arrow-u-right-top","tags":["redo"]},{"name":"mdi:arrow-u-right-top-bold","tags":["redo"]},{"name":"mdi:arrow-u-up-left","tags":[]},{"name":"mdi:arrow-u-up-left-bold","tags":[]},{"name":"mdi:arrow-u-up-right","tags":[]},{"name":"mdi:arrow-u-up-right-bold","tags":[]},{"name":"mdi:arrow-up","tags":["arrow","arrow upward","arrow top"]},{"name":"mdi:arrow-up-bold","tags":["arrow","arrow top bold"]},{"name":"mdi:arrow-up-bold-box","tags":["arrow","arrow top bold box"]},{"name":"mdi:arrow-up-bold-box-outline","tags":["arrow","arrow top bold box outline"]},{"name":"mdi:arrow-up-bold-circle","tags":["arrow","arrow top bold circle"]},{"name":"mdi:arrow-up-bold-circle-outline","tags":["arrow","arrow top bold circle outline"]},{"name":"mdi:arrow-up-bold-hexagon-outline","tags":["arrow","arrow top bold hexagon outline"]},{"name":"mdi:arrow-up-bold-outline","tags":["arrow","arrow top bold outline"]},{"name":"mdi:arrow-up-box","tags":["arrow"]},{"name":"mdi:arrow-up-circle","tags":["arrow","arrow top circle"]},{"name":"mdi:arrow-up-circle-outline","tags":["arrow","arrow top circle outline"]},{"name":"mdi:arrow-up-down","tags":["arrow"]},{"name":"mdi:arrow-up-down-bold","tags":["arrow"]},{"name":"mdi:arrow-up-down-bold-outline","tags":["arrow"]},{"name":"mdi:arrow-up-drop-circle","tags":["arrow","arrow top drop circle"]},{"name":"mdi:arrow-up-drop-circle-outline","tags":["arrow","arrow top drop circle outline"]},{"name":"mdi:arrow-up-left","tags":[]},{"name":"mdi:arrow-up-left-bold","tags":[]},{"name":"mdi:arrow-up-right","tags":[]},{"name":"mdi:arrow-up-right-bold","tags":[]},{"name":"mdi:arrow-up-thick","tags":["arrow","arrow top thick","arrow up bold","arrow top bold"]},{"name":"mdi:arrow-up-thin","tags":["arrow"]},{"name":"mdi:arrow-vertical-lock","tags":["lock","arrow","scroll vertical lock"]},{"name":"mdi:artboard","tags":["drawing / art","canvas","frame"]},{"name":"mdi:asterisk","tags":["required"]},{"name":"mdi:asterisk-circle-outline","tags":["required circle"]},{"name":"mdi:atom","tags":["science"]},{"name":"mdi:atom-variant","tags":["science","orbit"]},{"name":"mdi:attachment-check","tags":["attachment tick","paperclip check","paperclip tick"]},{"name":"mdi:attachment-lock","tags":["lock","paperclip lock"]},{"name":"mdi:attachment-minus","tags":["paperclip minus","paperclip subtract","attachment subtract"]},{"name":"mdi:attachment-off","tags":["paperclip off"]},{"name":"mdi:attachment-plus","tags":["paperclip plus","paperclip add","attachment add"]},{"name":"mdi:attachment-remove","tags":["paperclip remove"]},{"name":"mdi:audio-input-rca","tags":["audio"]},{"name":"mdi:audio-input-stereo-minijack","tags":["audio"]},{"name":"mdi:audio-input-xlr","tags":["audio"]},{"name":"mdi:audio-video","tags":["home automation","audio","av receiver"]},{"name":"mdi:audio-video-off","tags":["home automation","audio","av receiver off"]},{"name":"mdi:augmented-reality","tags":[]},{"name":"mdi:aurora","tags":["science","weather","aurora borealis","aurora australis","northern lights","southern lights","polar lights"]},{"name":"mdi:auto-download","tags":[]},{"name":"mdi:auto-mode","tags":[]},{"name":"mdi:autorenew-off","tags":["arrow","clockwise arrows off","circular arrows off","circle arrows off","sync off"]},{"name":"mdi:awning","tags":["home automation","marquise","sun shade"]},{"name":"mdi:awning-outline","tags":["home automation","marquise outline","sun shade outline"]},{"name":"mdi:axe","tags":["hardware / tools"]},{"name":"mdi:axe-battle","tags":["gaming / rpg"]},{"name":"mdi:axis","tags":[]},{"name":"mdi:axis-arrow","tags":["arrow","accelerometer","gyro"]},{"name":"mdi:axis-arrow-info","tags":["arrow"]},{"name":"mdi:axis-arrow-lock","tags":["lock","arrow"]},{"name":"mdi:axis-lock","tags":["lock"]},{"name":"mdi:axis-x-arrow","tags":["arrow"]},{"name":"mdi:axis-x-arrow-lock","tags":["lock","arrow"]},{"name":"mdi:axis-x-rotate-clockwise","tags":[]},{"name":"mdi:axis-x-rotate-counterclockwise","tags":[]},{"name":"mdi:axis-x-y-arrow-lock","tags":["lock","arrow"]},{"name":"mdi:axis-y-arrow","tags":["arrow"]},{"name":"mdi:axis-y-arrow-lock","tags":["lock","arrow"]},{"name":"mdi:axis-y-rotate-clockwise","tags":[]},{"name":"mdi:axis-y-rotate-counterclockwise","tags":[]},{"name":"mdi:axis-z-arrow","tags":["arrow"]},{"name":"mdi:axis-z-arrow-lock","tags":["lock","arrow"]},{"name":"mdi:axis-z-rotate-clockwise","tags":["vertical rotate clockwise"]},{"name":"mdi:axis-z-rotate-counterclockwise","tags":["vertical rotate counterclockwise"]},{"name":"mdi:baby-bottle","tags":["people / family"]},{"name":"mdi:baby-bottle-outline","tags":["people / family"]},{"name":"mdi:baby-buggy","tags":["people / family","stroller","pram","carriage"]},{"name":"mdi:baby-buggy-off","tags":["people / family"]},{"name":"mdi:baby-carriage-off","tags":["people / family","child friendly off","stroller off","pram off","buggy off"]},{"name":"mdi:backburger","tags":["hamburger menu back"]},{"name":"mdi:backspace-reverse","tags":["clear reverse","erase reverse"]},{"name":"mdi:backspace-reverse-outline","tags":["clear reverse outline","erase reverse outline"]},{"name":"mdi:bacteria","tags":["science","medical / hospital"]},{"name":"mdi:bacteria-outline","tags":["science","medical / hospital"]},{"name":"mdi:badge-account","tags":["account / user","user badge","person badge"]},{"name":"mdi:badge-account-alert","tags":["account / user","alert / error","user badge alert","person badge alert","account badge warning","user badge warning","person badge warning"]},{"name":"mdi:badge-account-alert-outline","tags":["account / user","alert / error","user badge alert outline","person badge alert outline","account badge warning outline","user badge warning outline","person badge warning outline"]},{"name":"mdi:badge-account-outline","tags":["account / user","user badge outline","person badge outline"]},{"name":"mdi:badminton","tags":["sport","shuttlecock"]},{"name":"mdi:bag-personal","tags":["transportation + flying","backpack"]},{"name":"mdi:bag-personal-off","tags":["transportation + flying","backpack off"]},{"name":"mdi:bag-personal-off-outline","tags":["transportation + flying","backpack off outline"]},{"name":"mdi:bag-personal-outline","tags":["transportation + flying","backpack outline"]},{"name":"mdi:bag-personal-tag","tags":["property tag"]},{"name":"mdi:bag-personal-tag-outline","tags":["property tag outline"]},{"name":"mdi:baguette","tags":["food / drink","bread","bakery","french baguette","loaf"]},{"name":"mdi:balloon","tags":["holiday","party balloon"]},{"name":"mdi:ballot-recount","tags":["vote recount"]},{"name":"mdi:ballot-recount-outline","tags":["vote recount outline"]},{"name":"mdi:bank-check","tags":["banking"]},{"name":"mdi:bank-circle","tags":["banking"]},{"name":"mdi:bank-circle-outline","tags":["banking"]},{"name":"mdi:bank-minus","tags":["banking"]},{"name":"mdi:bank-off","tags":["banking"]},{"name":"mdi:bank-off-outline","tags":["banking"]},{"name":"mdi:bank-plus","tags":["banking","bank add"]},{"name":"mdi:bank-remove","tags":["banking"]},{"name":"mdi:bank-transfer","tags":["banking"]},{"name":"mdi:bank-transfer-in","tags":["banking"]},{"name":"mdi:bank-transfer-out","tags":["banking"]},{"name":"mdi:barcode","tags":[]},{"name":"mdi:barcode-off","tags":[]},{"name":"mdi:barcode-scan","tags":["barcode scanner"]},{"name":"mdi:barley","tags":["agriculture","food / drink","grain","wheat","gluten"]},{"name":"mdi:barley-off","tags":["agriculture","gluten free","grain off","wheat off"]},{"name":"mdi:barn","tags":["agriculture","farm"]},{"name":"mdi:baseball","tags":["sport"]},{"name":"mdi:baseball-bat","tags":["sport"]},{"name":"mdi:baseball-diamond","tags":["sport"]},{"name":"mdi:baseball-diamond-outline","tags":["sport"]},{"name":"mdi:baseball-outline","tags":["sport"]},{"name":"mdi:bash","tags":["developer / languages"]},{"name":"mdi:basket-check","tags":["shopping"]},{"name":"mdi:basket-check-outline","tags":["shopping"]},{"name":"mdi:basket-fill","tags":["shopping","skip fill"]},{"name":"mdi:basket-minus","tags":["shopping","shopping basket minus","skip minus"]},{"name":"mdi:basket-minus-outline","tags":["shopping","shopping basket minus outline","skip minus outline"]},{"name":"mdi:basket-off","tags":["shopping","shopping basket off","skip off"]},{"name":"mdi:basket-off-outline","tags":["shopping","shopping basket off outline","skip off outline"]},{"name":"mdi:basket-plus","tags":["shopping","shopping basket plus","skip plus"]},{"name":"mdi:basket-plus-outline","tags":["shopping","shopping basket plus outline","skip plus outline"]},{"name":"mdi:basket-remove","tags":["shopping","shopping basket remove","skip remove"]},{"name":"mdi:basket-remove-outline","tags":["shopping","shopping basket remove outline","skip remove outline"]},{"name":"mdi:basket-unfill","tags":["shopping"]},{"name":"mdi:basketball-hoop","tags":["sport"]},{"name":"mdi:basketball-hoop-outline","tags":["sport"]},{"name":"mdi:bat","tags":["holiday","animal"]},{"name":"mdi:battery-10-bluetooth","tags":["battery"]},{"name":"mdi:battery-20-bluetooth","tags":["battery"]},{"name":"mdi:battery-30-bluetooth","tags":["battery"]},{"name":"mdi:battery-40-bluetooth","tags":["battery"]},{"name":"mdi:battery-50-bluetooth","tags":["battery"]},{"name":"mdi:battery-60-bluetooth","tags":["battery"]},{"name":"mdi:battery-70-bluetooth","tags":["battery"]},{"name":"mdi:battery-80-bluetooth","tags":["battery"]},{"name":"mdi:battery-90-bluetooth","tags":["battery"]},{"name":"mdi:battery-alert-bluetooth","tags":["alert / error","battery","battery warning bluetooth"]},{"name":"mdi:battery-alert-variant","tags":["battery","alert / error"]},{"name":"mdi:battery-alert-variant-outline","tags":["battery","alert / error"]},{"name":"mdi:battery-arrow-down","tags":["battery"]},{"name":"mdi:battery-arrow-down-outline","tags":["battery"]},{"name":"mdi:battery-arrow-up","tags":["battery"]},{"name":"mdi:battery-arrow-up-outline","tags":["battery"]},{"name":"mdi:battery-bluetooth","tags":["battery","battery bluetooth 100","battery bluetooth full"]},{"name":"mdi:battery-bluetooth-variant","tags":["battery"]},{"name":"mdi:battery-charging-high","tags":["battery"]},{"name":"mdi:battery-charging-low","tags":["battery"]},{"name":"mdi:battery-charging-medium","tags":["battery"]},{"name":"mdi:battery-charging-wireless","tags":["battery","home automation","battery charging wireless full","battery charging wireless 100"]},{"name":"mdi:battery-charging-wireless-10","tags":["battery","home automation"]},{"name":"mdi:battery-charging-wireless-20","tags":["battery","home automation"]},{"name":"mdi:battery-charging-wireless-30","tags":["battery","home automation"]},{"name":"mdi:battery-charging-wireless-40","tags":["battery","home automation"]},{"name":"mdi:battery-charging-wireless-50","tags":["battery","home automation"]},{"name":"mdi:battery-charging-wireless-60","tags":["battery","home automation"]},{"name":"mdi:battery-charging-wireless-70","tags":["battery","home automation"]},{"name":"mdi:battery-charging-wireless-80","tags":["battery","home automation"]},{"name":"mdi:battery-charging-wireless-90","tags":["battery","home automation"]},{"name":"mdi:battery-charging-wireless-alert","tags":["battery","home automation","alert / error","battery charging wireless warning"]},{"name":"mdi:battery-charging-wireless-outline","tags":["battery","home automation","battery charging wireless empty","battery charging wireless 0"]},{"name":"mdi:battery-check","tags":["battery"]},{"name":"mdi:battery-check-outline","tags":["battery"]},{"name":"mdi:battery-clock","tags":["battery","home automation","date / time","battery full clock","battery 100 clock"]},{"name":"mdi:battery-clock-outline","tags":["battery","home automation","date / time","batter 0 clock","battery empty clock"]},{"name":"mdi:battery-heart","tags":["battery"]},{"name":"mdi:battery-heart-outline","tags":["battery"]},{"name":"mdi:battery-heart-variant","tags":["battery"]},{"name":"mdi:battery-high","tags":["battery"]},{"name":"mdi:battery-lock","tags":["battery","lock"]},{"name":"mdi:battery-lock-open","tags":["battery","lock"]},{"name":"mdi:battery-low","tags":["battery"]},{"name":"mdi:battery-medium","tags":["battery"]},{"name":"mdi:battery-minus","tags":["battery"]},{"name":"mdi:battery-minus-outline","tags":["battery"]},{"name":"mdi:battery-minus-variant","tags":["battery","home automation"]},{"name":"mdi:battery-negative","tags":["battery","home automation"]},{"name":"mdi:battery-off","tags":["battery"]},{"name":"mdi:battery-off-outline","tags":["battery"]},{"name":"mdi:battery-plus","tags":["battery"]},{"name":"mdi:battery-plus-outline","tags":["battery"]},{"name":"mdi:battery-plus-variant","tags":["battery","home automation","battery saver","battery add"]},{"name":"mdi:battery-positive","tags":["battery","home automation"]},{"name":"mdi:battery-remove","tags":["battery"]},{"name":"mdi:battery-remove-outline","tags":["battery"]},{"name":"mdi:battery-sync","tags":["battery","battery saver","battery recycle","battery eco"]},{"name":"mdi:battery-sync-outline","tags":["battery","battery saver outline","battery eco outline","battery recycle outline"]},{"name":"mdi:battery-unknown-bluetooth","tags":["battery"]},{"name":"mdi:beach","tags":["places","parasol"]},{"name":"mdi:beaker","tags":["science"]},{"name":"mdi:beaker-alert","tags":["alert / error","science"]},{"name":"mdi:beaker-alert-outline","tags":["alert / error","science"]},{"name":"mdi:beaker-check","tags":["science"]},{"name":"mdi:beaker-check-outline","tags":["science"]},{"name":"mdi:beaker-minus","tags":["science"]},{"name":"mdi:beaker-minus-outline","tags":["science"]},{"name":"mdi:beaker-outline","tags":["science"]},{"name":"mdi:beaker-plus","tags":["science"]},{"name":"mdi:beaker-plus-outline","tags":["science"]},{"name":"mdi:beaker-question","tags":["science"]},{"name":"mdi:beaker-question-outline","tags":["science"]},{"name":"mdi:beaker-remove","tags":["science"]},{"name":"mdi:beaker-remove-outline","tags":["science"]},{"name":"mdi:bed-clock","tags":["date / time","bed schedule","bed time","sleep schedule","sleep time"]},{"name":"mdi:bed-double","tags":["home automation","holiday","bedroom"]},{"name":"mdi:bed-empty","tags":["home automation","holiday"]},{"name":"mdi:bed-king-outline","tags":["home automation","holiday","bedroom outline"]},{"name":"mdi:bed-queen","tags":["home automation","holiday","bedroom"]},{"name":"mdi:bed-queen-outline","tags":["home automation","holiday","bedroom outline"]},{"name":"mdi:bed-single","tags":["home automation","holiday","bedroom"]},{"name":"mdi:bed-single-outline","tags":["home automation","holiday","bedroom outline"]},{"name":"mdi:beehive-off-outline","tags":["nature","agriculture"]},{"name":"mdi:beehive-outline","tags":["nature","agriculture","honey outline"]},{"name":"mdi:beekeeper","tags":["nature","agriculture","apiarists","apiculturists","honey farmer"]},{"name":"mdi:beer","tags":["food / drink","pint","pub","bar","drink","cup full"]},{"name":"mdi:beer-outline","tags":["food / drink","drink outline","cup full outline","pint outline","pub outline","bar outline"]},{"name":"mdi:bell-alert","tags":["alert / error","notification","bell warning"]},{"name":"mdi:bell-alert-outline","tags":["alert / error","notification"]},{"name":"mdi:bell-badge","tags":["notification","bell notification"]},{"name":"mdi:bell-badge-outline","tags":["notification","bell notification outline"]},{"name":"mdi:bell-cancel","tags":["notification"]},{"name":"mdi:bell-cancel-outline","tags":["notification"]},{"name":"mdi:bell-check","tags":["notification"]},{"name":"mdi:bell-check-outline","tags":["notification"]},{"name":"mdi:bell-cog","tags":["notification","settings","bell settings","notification settings"]},{"name":"mdi:bell-cog-outline","tags":["notification","settings","bell settings outline","notification settings outline"]},{"name":"mdi:bell-minus","tags":["notification"]},{"name":"mdi:bell-minus-outline","tags":["notification"]},{"name":"mdi:bell-plus","tags":["notification","add alert","bell add"]},{"name":"mdi:bell-plus-outline","tags":["notification","bell add outline","add alert outline"]},{"name":"mdi:bell-remove","tags":["notification"]},{"name":"mdi:bell-remove-outline","tags":["notification"]},{"name":"mdi:bench","tags":[]},{"name":"mdi:bench-back","tags":[]},{"name":"mdi:beta","tags":["alpha / numeric"]},{"name":"mdi:betamax","tags":[]},{"name":"mdi:bicycle","tags":["transportation + other","sport","bike","cycling"]},{"name":"mdi:bicycle-basket","tags":["transportation + other","sport","bike basket"]},{"name":"mdi:bicycle-cargo","tags":["transportation + other","sport","bike cargo"]},{"name":"mdi:bicycle-electric","tags":["transportation + other","bike electric"]},{"name":"mdi:bicycle-penny-farthing","tags":["transportation + other","sport","bicycle high wheel","bicycle antique"]},{"name":"mdi:bike-fast","tags":["transportation + other","sport","velocity"]},{"name":"mdi:bike-pedal","tags":["transportation + other","sport","bike pedal flat"]},{"name":"mdi:bike-pedal-clipless","tags":["transportation + other","sport"]},{"name":"mdi:bike-pedal-mountain","tags":["transportation + other","sport"]},{"name":"mdi:billboard","tags":[]},{"name":"mdi:billiards","tags":["sport","pool","eight ball"]},{"name":"mdi:billiards-rack","tags":["sport","pool table","pool rack","snooker rack","pool triangle","billiards triangle","snooker triangle"]},{"name":"mdi:binoculars","tags":[]},{"name":"mdi:bio","tags":[]},{"name":"mdi:biohazard","tags":["science"]},{"name":"mdi:bird","tags":["animal"]},{"name":"mdi:blinds","tags":["home automation","roller shade closed","window closed"]},{"name":"mdi:blinds-open","tags":["home automation","roller shade open","window open"]},{"name":"mdi:block-helper","tags":[]},{"name":"mdi:blood-bag","tags":["medical / hospital"]},{"name":"mdi:bluetooth","tags":[]},{"name":"mdi:bolt","tags":["hardware / tools"]},{"name":"mdi:bomb","tags":["gaming / rpg"]},{"name":"mdi:bomb-off","tags":["gaming / rpg"]},{"name":"mdi:bone","tags":["animal","holiday"]},{"name":"mdi:bone-off","tags":["animal","holiday"]},{"name":"mdi:book-account","tags":["account / user"]},{"name":"mdi:book-account-outline","tags":["account / user"]},{"name":"mdi:book-alert","tags":["alert / error"]},{"name":"mdi:book-alert-outline","tags":["alert / error"]},{"name":"mdi:book-alphabet","tags":["dictionary"]},{"name":"mdi:book-arrow-down","tags":[]},{"name":"mdi:book-arrow-down-outline","tags":[]},{"name":"mdi:book-arrow-left","tags":[]},{"name":"mdi:book-arrow-left-outline","tags":[]},{"name":"mdi:book-arrow-right","tags":[]},{"name":"mdi:book-arrow-right-outline","tags":[]},{"name":"mdi:book-arrow-up","tags":[]},{"name":"mdi:book-arrow-up-outline","tags":[]},{"name":"mdi:book-cancel","tags":[]},{"name":"mdi:book-cancel-outline","tags":[]},{"name":"mdi:book-check","tags":[]},{"name":"mdi:book-check-outline","tags":[]},{"name":"mdi:book-clock","tags":["date / time","book schedule","book time"]},{"name":"mdi:book-clock-outline","tags":["date / time","book schedule","book time"]},{"name":"mdi:book-cog","tags":["settings","book settings"]},{"name":"mdi:book-cog-outline","tags":["settings","book settings outline"]},{"name":"mdi:book-cross","tags":["religion","bible"]},{"name":"mdi:book-edit","tags":["edit / modify"]},{"name":"mdi:book-edit-outline","tags":["edit / modify"]},{"name":"mdi:book-education","tags":[]},{"name":"mdi:book-education-outline","tags":[]},{"name":"mdi:book-heart","tags":["book favorite","book love"]},{"name":"mdi:book-heart-outline","tags":["book favorite outline","book love outline"]},{"name":"mdi:book-information-variant","tags":["encyclopedia"]},{"name":"mdi:book-lock","tags":["lock","book secure"]},{"name":"mdi:book-lock-open","tags":["lock","book unsecure"]},{"name":"mdi:book-lock-open-outline","tags":["lock"]},{"name":"mdi:book-lock-outline","tags":["lock","book secure outline"]},{"name":"mdi:book-marker","tags":["navigation","book location"]},{"name":"mdi:book-marker-outline","tags":["navigation","book location outline"]},{"name":"mdi:book-minus","tags":[]},{"name":"mdi:book-minus-multiple","tags":["books minus"]},{"name":"mdi:book-minus-multiple-outline","tags":[]},{"name":"mdi:book-minus-outline","tags":[]},{"name":"mdi:book-multiple","tags":["books"]},{"name":"mdi:book-multiple-outline","tags":[]},{"name":"mdi:book-music","tags":["audio","music","audio book"]},{"name":"mdi:book-music-outline","tags":["music"]},{"name":"mdi:book-off","tags":[]},{"name":"mdi:book-off-outline","tags":[]},{"name":"mdi:book-play","tags":[]},{"name":"mdi:book-play-outline","tags":[]},{"name":"mdi:book-plus","tags":["book add"]},{"name":"mdi:book-plus-multiple","tags":["books plus","book multiple add","books add"]},{"name":"mdi:book-plus-multiple-outline","tags":[]},{"name":"mdi:book-plus-outline","tags":[]},{"name":"mdi:book-refresh","tags":[]},{"name":"mdi:book-refresh-outline","tags":[]},{"name":"mdi:book-remove","tags":[]},{"name":"mdi:book-remove-multiple","tags":["books remove"]},{"name":"mdi:book-remove-multiple-outline","tags":[]},{"name":"mdi:book-remove-outline","tags":[]},{"name":"mdi:book-search","tags":[]},{"name":"mdi:book-search-outline","tags":[]},{"name":"mdi:book-settings","tags":["settings"]},{"name":"mdi:book-settings-outline","tags":["settings"]},{"name":"mdi:book-sync","tags":[]},{"name":"mdi:book-sync-outline","tags":[]},{"name":"mdi:bookmark-box","tags":[]},{"name":"mdi:bookmark-box-multiple-outline","tags":["collections bookmark outline","library bookmark outline"]},{"name":"mdi:bookmark-box-outline","tags":[]},{"name":"mdi:bookmark-check-outline","tags":["bookmark success outline"]},{"name":"mdi:bookmark-minus","tags":[]},{"name":"mdi:bookmark-minus-outline","tags":[]},{"name":"mdi:bookmark-music","tags":["music"]},{"name":"mdi:bookmark-music-outline","tags":["music"]},{"name":"mdi:bookmark-off","tags":[]},{"name":"mdi:bookmark-off-outline","tags":[]},{"name":"mdi:bookmark-plus","tags":["bookmark add"]},{"name":"mdi:bookmark-remove","tags":[]},{"name":"mdi:bookmark-remove-outline","tags":[]},{"name":"mdi:bookshelf","tags":[]},{"name":"mdi:boom-gate","tags":["transportation + road","home automation","boom arm","boom barrier","arm barrier","barrier","automatic gate"]},{"name":"mdi:boom-gate-alert","tags":["alert / error","transportation + road","boom arm alert","boom barrier alert","arm barrier alert","barrier alert","automatic gate alert"]},{"name":"mdi:boom-gate-alert-outline","tags":["alert / error","transportation + road","boom arm alert outline","boom barrier alert outline","arm barrier alert outline","barrier alert outline","automatic gate alert outline"]},{"name":"mdi:boom-gate-arrow-down","tags":["transportation + road","boom arm down","boom barrier down","arm barrier down","barrier down","automatic gate down"]},{"name":"mdi:boom-gate-arrow-down-outline","tags":["transportation + road","boom arm down outline","boom barrier down outline","arm barrier down outline","barrier down outline","automatic gate down outline"]},{"name":"mdi:boom-gate-arrow-up","tags":["transportation + road","boom arm up","boom barrier up","arm barrier up","barrier up","automatic gate up"]},{"name":"mdi:boom-gate-arrow-up-outline","tags":["transportation + road","boom arm up outline","boom barrier up outline","arm barrier up outline","barrier up outline","automatic gate up outline"]},{"name":"mdi:boom-gate-outline","tags":["transportation + road","home automation","boom arm outline","boom barrier outline","arm barrier outline","barrier outline","automatic gate outline"]},{"name":"mdi:boom-gate-up","tags":["transportation + road","home automation","boom arm up","boom barrier up","arm barrier up","barrier up","automatic gate up"]},{"name":"mdi:boom-gate-up-outline","tags":["transportation + road","home automation","boom arm up outline","boom barrier up outline","arm barrier up outline","barrier up outline","automatic gate up outline"]},{"name":"mdi:boomerang","tags":["gaming / rpg"]},{"name":"mdi:border-all-variant","tags":["text / content / format"]},{"name":"mdi:border-bottom-variant","tags":["text / content / format"]},{"name":"mdi:border-left-variant","tags":["text / content / format"]},{"name":"mdi:border-none-variant","tags":["text / content / format"]},{"name":"mdi:border-radius","tags":["text / content / format","border round corners"]},{"name":"mdi:border-right-variant","tags":["text / content / format"]},{"name":"mdi:border-top-variant","tags":["text / content / format"]},{"name":"mdi:bottle-soda","tags":["food / drink","bottle coke","bottle pop"]},{"name":"mdi:bottle-soda-classic","tags":["food / drink","bottle coke classic","bottle pop classic"]},{"name":"mdi:bottle-soda-classic-outline","tags":[]},{"name":"mdi:bottle-soda-outline","tags":["food / drink","bottle coke outline","bottle pop outline"]},{"name":"mdi:bottle-tonic","tags":["science","flask"]},{"name":"mdi:bottle-tonic-outline","tags":["science","flask outline"]},{"name":"mdi:bottle-tonic-plus","tags":["gaming / rpg","health potion"]},{"name":"mdi:bottle-tonic-plus-outline","tags":["gaming / rpg","health potion outline"]},{"name":"mdi:bottle-tonic-skull","tags":["gaming / rpg","holiday","poison","moonshine"]},{"name":"mdi:bottle-tonic-skull-outline","tags":["gaming / rpg","holiday","poison outline","moonshine outline"]},{"name":"mdi:bottle-wine","tags":["food / drink"]},{"name":"mdi:bottle-wine-outline","tags":["food / drink"]},{"name":"mdi:bow-arrow","tags":["gaming / rpg","sport"]},{"name":"mdi:bow-tie","tags":["clothing"]},{"name":"mdi:bowl","tags":["food / drink"]},{"name":"mdi:bowl-mix","tags":["food / drink","mixing bowl"]},{"name":"mdi:bowl-mix-outline","tags":["food / drink","mixing bowl outline"]},{"name":"mdi:bowl-outline","tags":["food / drink"]},{"name":"mdi:bowling","tags":["sport"]},{"name":"mdi:box-cutter","tags":["hardware / tools","stanley knife"]},{"name":"mdi:box-cutter-off","tags":[]},{"name":"mdi:box-shadow","tags":[]},{"name":"mdi:boxing-glove","tags":["sport"]},{"name":"mdi:braille","tags":["touch reading","hand reading"]},{"name":"mdi:brain","tags":["medical / hospital"]},{"name":"mdi:bread-slice","tags":["food / drink"]},{"name":"mdi:bread-slice-outline","tags":["food / drink"]},{"name":"mdi:bridge","tags":["places"]},{"name":"mdi:briefcase-account","tags":["account / user","briefcase person","briefcase user"]},{"name":"mdi:briefcase-account-outline","tags":["account / user","briefcase person outline","briefcase user outline"]},{"name":"mdi:briefcase-arrow-left-right","tags":["briefcase transfer","briefcase exchange","briefcase swap"]},{"name":"mdi:briefcase-arrow-left-right-outline","tags":["briefcase exchange outline","briefcase transfer outline","briefcase swap outline"]},{"name":"mdi:briefcase-arrow-up-down","tags":["briefcase exchange","briefcase transfer","briefcase swap"]},{"name":"mdi:briefcase-arrow-up-down-outline","tags":["briefcase exchange outline","briefcase transfer outline","briefcase swap outline"]},{"name":"mdi:briefcase-check-outline","tags":[]},{"name":"mdi:briefcase-clock","tags":["date / time"]},{"name":"mdi:briefcase-clock-outline","tags":["date / time"]},{"name":"mdi:briefcase-download-outline","tags":[]},{"name":"mdi:briefcase-edit","tags":["edit / modify"]},{"name":"mdi:briefcase-edit-outline","tags":["edit / modify"]},{"name":"mdi:briefcase-eye","tags":["briefcase view"]},{"name":"mdi:briefcase-eye-outline","tags":["briefcase view outline"]},{"name":"mdi:briefcase-minus","tags":[]},{"name":"mdi:briefcase-minus-outline","tags":[]},{"name":"mdi:briefcase-off","tags":[]},{"name":"mdi:briefcase-off-outline","tags":[]},{"name":"mdi:briefcase-plus","tags":["briefcase add"]},{"name":"mdi:briefcase-plus-outline","tags":["briefcase add outline"]},{"name":"mdi:briefcase-remove","tags":[]},{"name":"mdi:briefcase-remove-outline","tags":[]},{"name":"mdi:briefcase-search","tags":[]},{"name":"mdi:briefcase-search-outline","tags":[]},{"name":"mdi:briefcase-upload","tags":[]},{"name":"mdi:briefcase-upload-outline","tags":[]},{"name":"mdi:briefcase-variant-off","tags":[]},{"name":"mdi:briefcase-variant-off-outline","tags":[]},{"name":"mdi:brightness-percent","tags":["shopping","discount","sale"]},{"name":"mdi:broom","tags":[]},{"name":"mdi:brush-off","tags":[]},{"name":"mdi:bucket","tags":[]},{"name":"mdi:bucket-outline","tags":[]},{"name":"mdi:buffet","tags":["home automation","sideboard"]},{"name":"mdi:bug-check","tags":["animal","bug tick"]},{"name":"mdi:bug-check-outline","tags":["animal","bug tick outline"]},{"name":"mdi:bug-pause","tags":[]},{"name":"mdi:bug-pause-outline","tags":[]},{"name":"mdi:bug-play","tags":["bug start"]},{"name":"mdi:bug-play-outline","tags":[]},{"name":"mdi:bug-stop","tags":[]},{"name":"mdi:bug-stop-outline","tags":[]},{"name":"mdi:bugle","tags":["automotive","music","car horn"]},{"name":"mdi:bulkhead-light","tags":["home automation"]},{"name":"mdi:bulldozer","tags":["hardware / tools"]},{"name":"mdi:bullet","tags":[]},{"name":"mdi:bulletin-board","tags":["notice board"]},{"name":"mdi:bullhorn-variant","tags":["announcement","megaphone","loudspeaker"]},{"name":"mdi:bullhorn-variant-outline","tags":["announcement outline","megaphone outline","loudspeaker outline"]},{"name":"mdi:bullseye","tags":["sport","target"]},{"name":"mdi:bullseye-arrow","tags":["sport","target arrow"]},{"name":"mdi:bunk-bed","tags":["home automation"]},{"name":"mdi:bunk-bed-outline","tags":["home automation"]},{"name":"mdi:bus-articulated-end","tags":["transportation + road"]},{"name":"mdi:bus-articulated-front","tags":["transportation + road"]},{"name":"mdi:bus-double-decker","tags":["transportation + road"]},{"name":"mdi:bus-electric","tags":["transportation + road"]},{"name":"mdi:bus-marker","tags":["navigation","bus location","bus stop"]},{"name":"mdi:bus-multiple","tags":["transportation + road","fleet"]},{"name":"mdi:bus-school","tags":["transportation + road","education"]},{"name":"mdi:bus-side","tags":["transportation + road"]},{"name":"mdi:bus-stop","tags":["transportation + road","navigation"]},{"name":"mdi:bus-stop-covered","tags":["transportation + road","navigation"]},{"name":"mdi:bus-stop-uncovered","tags":["transportation + road","navigation"]},{"name":"mdi:butterfly","tags":["nature","animal"]},{"name":"mdi:butterfly-outline","tags":["nature","animal"]},{"name":"mdi:button-cursor","tags":["form"]},{"name":"mdi:button-pointer","tags":["form"]},{"name":"mdi:cabin-a-frame","tags":["home automation"]},{"name":"mdi:cable-data","tags":[]},{"name":"mdi:cactus","tags":["nature"]},{"name":"mdi:calculator","tags":["math"]},{"name":"mdi:calendar-account","tags":["date / time","account / user","calendar user"]},{"name":"mdi:calendar-account-outline","tags":["date / time","account / user","calendar user outline"]},{"name":"mdi:calendar-alert","tags":["date / time","alert / error","event alert","calendar warning"]},{"name":"mdi:calendar-alert-outline","tags":["date / time","alert / error"]},{"name":"mdi:calendar-arrow-left","tags":["date / time","reschedule"]},{"name":"mdi:calendar-arrow-right","tags":["date / time","reschedule"]},{"name":"mdi:calendar-badge","tags":["date / time"]},{"name":"mdi:calendar-badge-outline","tags":["date / time"]},{"name":"mdi:calendar-blank","tags":["date / time","calendar today"]},{"name":"mdi:calendar-blank-multiple","tags":["date / time"]},{"name":"mdi:calendar-clock","tags":["date / time","event clock","event time","calendar time"]},{"name":"mdi:calendar-clock-outline","tags":["date / time"]},{"name":"mdi:calendar-collapse-horizontal","tags":["date / time"]},{"name":"mdi:calendar-collapse-horizontal-outline","tags":["date / time"]},{"name":"mdi:calendar-cursor","tags":["date / time"]},{"name":"mdi:calendar-cursor-outline","tags":["date / time"]},{"name":"mdi:calendar-edit","tags":["date / time","edit / modify","event edit"]},{"name":"mdi:calendar-edit-outline","tags":["date / time","edit / modify"]},{"name":"mdi:calendar-end","tags":["date / time"]},{"name":"mdi:calendar-end-outline","tags":["date / time"]},{"name":"mdi:calendar-expand-horizontal","tags":["date / time"]},{"name":"mdi:calendar-expand-horizontal-outline","tags":["date / time"]},{"name":"mdi:calendar-export","tags":["date / time"]},{"name":"mdi:calendar-export-outline","tags":["date / time"]},{"name":"mdi:calendar-filter","tags":["date / time"]},{"name":"mdi:calendar-filter-outline","tags":["date / time","event week end outline"]},{"name":"mdi:calendar-heart","tags":["date / time","event heart"]},{"name":"mdi:calendar-heart-outline","tags":["date / time"]},{"name":"mdi:calendar-import","tags":["date / time"]},{"name":"mdi:calendar-import-outline","tags":["date / time"]},{"name":"mdi:calendar-lock","tags":["date / time","lock"]},{"name":"mdi:calendar-lock-open","tags":["lock","date / time"]},{"name":"mdi:calendar-lock-open-outline","tags":["lock","date / time"]},{"name":"mdi:calendar-lock-outline","tags":["date / time","lock"]},{"name":"mdi:calendar-minus","tags":["date / time","event minus"]},{"name":"mdi:calendar-minus-outline","tags":["date / time"]},{"name":"mdi:calendar-month","tags":["date / time"]},{"name":"mdi:calendar-month-outline","tags":["date / time"]},{"name":"mdi:calendar-multiple","tags":["date / time","event multiple","calendars","events"]},{"name":"mdi:calendar-multiple-check","tags":["date / time","event multiple check","calendar multiple tick","calendars check","calendars tick","event multiple tick","events check","events tick"]},{"name":"mdi:calendar-multiselect","tags":["date / time"]},{"name":"mdi:calendar-multiselect-outline","tags":["date / time"]},{"name":"mdi:calendar-plus","tags":["date / time","event plus","calendar add","event add"]},{"name":"mdi:calendar-plus-outline","tags":["date / time"]},{"name":"mdi:calendar-question","tags":["date / time","calendar rsvp","event question","calendar help"]},{"name":"mdi:calendar-question-outline","tags":["date / time","calendar help outline"]},{"name":"mdi:calendar-refresh","tags":["date / time","calendar repeat"]},{"name":"mdi:calendar-refresh-outline","tags":["date / time","calendar repeat outline"]},{"name":"mdi:calendar-search","tags":["date / time","event search"]},{"name":"mdi:calendar-search-outline","tags":["date / time"]},{"name":"mdi:calendar-star","tags":["date / time","event star","calendar favorite"]},{"name":"mdi:calendar-star-four-points","tags":["date / time","calendar auto","event star four points","event auto"]},{"name":"mdi:calendar-star-outline","tags":["date / time"]},{"name":"mdi:calendar-start","tags":["date / time"]},{"name":"mdi:calendar-start-outline","tags":["date / time"]},{"name":"mdi:calendar-sync","tags":["date / time","calendar repeat"]},{"name":"mdi:calendar-sync-outline","tags":["date / time","calendar repeat outline"]},{"name":"mdi:calendar-today-outline","tags":["date / time","calendar day outline"]},{"name":"mdi:calendar-week","tags":["date / time","event week"]},{"name":"mdi:calendar-week-begin","tags":["date / time","event week begin"]},{"name":"mdi:calendar-week-begin-outline","tags":["date / time","event week begin outline"]},{"name":"mdi:calendar-week-outline","tags":["date / time","event week outline"]},{"name":"mdi:calendar-weekend","tags":["date / time"]},{"name":"mdi:calendar-weekend-outline","tags":["date / time"]},{"name":"mdi:camcorder","tags":["video / movie"]},{"name":"mdi:camcorder-off","tags":["video / movie"]},{"name":"mdi:camera-document","tags":["photography","overhead projector"]},{"name":"mdi:camera-document-off","tags":["photography","overhead projector off"]},{"name":"mdi:camera-flip","tags":["photography","camera sync","camera refresh"]},{"name":"mdi:camera-flip-outline","tags":["photography","camera sync outline","camera refresh outline"]},{"name":"mdi:camera-gopro","tags":["photography","device / tech"]},{"name":"mdi:camera-lock","tags":["photography","lock"]},{"name":"mdi:camera-lock-open","tags":["photography"]},{"name":"mdi:camera-lock-open-outline","tags":["photography"]},{"name":"mdi:camera-lock-outline","tags":["photography","lock"]},{"name":"mdi:camera-marker","tags":["photography","navigation","camera location"]},{"name":"mdi:camera-marker-outline","tags":["photography","navigation","camera location outline"]},{"name":"mdi:camera-metering-center","tags":["photography","camera metering centre"]},{"name":"mdi:camera-metering-matrix","tags":["photography"]},{"name":"mdi:camera-metering-partial","tags":["photography"]},{"name":"mdi:camera-metering-spot","tags":["photography"]},{"name":"mdi:camera-off","tags":["photography"]},{"name":"mdi:camera-off-outline","tags":["photography"]},{"name":"mdi:camera-retake","tags":["photography"]},{"name":"mdi:camera-retake-outline","tags":["photography"]},{"name":"mdi:camera-timer","tags":["date / time","photography"]},{"name":"mdi:campfire","tags":[]},{"name":"mdi:candelabra","tags":["home automation","holiday","candle","candelabrum"]},{"name":"mdi:candelabra-fire","tags":["home automation","holiday","candelabrum fire","candelabrum flame","candelabra flame","candle fire","candle flame"]},{"name":"mdi:candy","tags":["food / drink","treat","chocolate"]},{"name":"mdi:candy-off","tags":["food / drink","chocolate off","treat off"]},{"name":"mdi:candy-off-outline","tags":["food / drink","gaming / rpg","chocolate off outline","treat off outline","navi off"]},{"name":"mdi:candy-outline","tags":["food / drink","gaming / rpg","chocolate outline","treat outline","navi","hey listen","fairy"]},{"name":"mdi:candycane","tags":["holiday","food / drink"]},{"name":"mdi:cannabis","tags":["nature","medical / hospital","weed","pot","marijuana"]},{"name":"mdi:cannabis-off","tags":[]},{"name":"mdi:caps-lock","tags":["text / content / format"]},{"name":"mdi:car-2-plus","tags":["transportation + road","automotive","hov lane","high occupancy vehicle lane","carpool lane"]},{"name":"mdi:car-3-plus","tags":["transportation + road","automotive","hov lane","high occupancy vehicle lane","carpool lane"]},{"name":"mdi:car-arrow-left","tags":["automotive","transportation + road"]},{"name":"mdi:car-arrow-right","tags":["automotive","transportation + road"]},{"name":"mdi:car-back","tags":["automotive","transportation + road"]},{"name":"mdi:car-battery","tags":["battery","automotive"]},{"name":"mdi:car-brake-abs","tags":["automotive","anti lock brake system","anti lock braking system"]},{"name":"mdi:car-brake-alert","tags":["automotive","alert / error","car parking brake","car handbrake","car hand brake","car emergency brake","car brake warning"]},{"name":"mdi:car-brake-fluid-level","tags":["automotive"]},{"name":"mdi:car-brake-hold","tags":["automotive"]},{"name":"mdi:car-brake-low-pressure","tags":["automotive"]},{"name":"mdi:car-brake-parking","tags":["automotive"]},{"name":"mdi:car-brake-retarder","tags":["automotive"]},{"name":"mdi:car-brake-temperature","tags":["automotive"]},{"name":"mdi:car-brake-worn-linings","tags":["automotive"]},{"name":"mdi:car-child-seat","tags":["automotive","people / family"]},{"name":"mdi:car-clock","tags":["date / time","automotive"]},{"name":"mdi:car-clutch","tags":["automotive"]},{"name":"mdi:car-cog","tags":["automotive","settings","transportation + road","car settings"]},{"name":"mdi:car-connected","tags":["transportation + road","automotive"]},{"name":"mdi:car-convertible","tags":["transportation + road","automotive"]},{"name":"mdi:car-coolant-level","tags":["automotive"]},{"name":"mdi:car-cruise-control","tags":["automotive"]},{"name":"mdi:car-defrost-front","tags":["automotive"]},{"name":"mdi:car-defrost-rear","tags":["automotive"]},{"name":"mdi:car-door","tags":["automotive"]},{"name":"mdi:car-door-lock","tags":["automotive","lock"]},{"name":"mdi:car-emergency","tags":["transportation + road","automotive","car police"]},{"name":"mdi:car-esp","tags":["automotive","electronic stability program"]},{"name":"mdi:car-estate","tags":["transportation + road","automotive","car suv","car sports utility vehicle"]},{"name":"mdi:car-hatchback","tags":["transportation + road","automotive"]},{"name":"mdi:car-info","tags":["automotive"]},{"name":"mdi:car-key","tags":["transportation + road","automotive","car rental","rent a car"]},{"name":"mdi:car-lifted-pickup","tags":["automotive","agriculture"]},{"name":"mdi:car-light-alert","tags":["alert / error","automotive"]},{"name":"mdi:car-light-dimmed","tags":["automotive","head light dimmed","low beam"]},{"name":"mdi:car-light-fog","tags":["automotive","head light fog"]},{"name":"mdi:car-light-high","tags":["automotive","head light high","high beam"]},{"name":"mdi:car-limousine","tags":["transportation + road","automotive"]},{"name":"mdi:car-multiple","tags":["transportation + road","automotive"]},{"name":"mdi:car-off","tags":["automotive"]},{"name":"mdi:car-parking-lights","tags":["automotive"]},{"name":"mdi:car-pickup","tags":["transportation + road","automotive","agriculture"]},{"name":"mdi:car-search","tags":["automotive","car find"]},{"name":"mdi:car-search-outline","tags":["automotive","car find outline"]},{"name":"mdi:car-seat","tags":["automotive"]},{"name":"mdi:car-seat-cooler","tags":["automotive"]},{"name":"mdi:car-seat-heater","tags":["automotive"]},{"name":"mdi:car-select","tags":["automotive","car location"]},{"name":"mdi:car-settings","tags":["automotive","settings"]},{"name":"mdi:car-shift-pattern","tags":["automotive","car transmission","car manual transmission"]},{"name":"mdi:car-side","tags":["transportation + road","automotive","car saloon"]},{"name":"mdi:car-speed-limiter","tags":["automotive"]},{"name":"mdi:car-sports","tags":["transportation + road","sport","automotive"]},{"name":"mdi:car-tire-alert","tags":["automotive","alert / error","car tyre alert","car tyre warning","car tire warning"]},{"name":"mdi:car-traction-control","tags":["automotive"]},{"name":"mdi:car-turbocharger","tags":["automotive"]},{"name":"mdi:car-windshield","tags":["automotive","car front glass"]},{"name":"mdi:car-windshield-outline","tags":["automotive","car front glass outline"]},{"name":"mdi:car-wireless","tags":["automotive","car autonomous","car self driving","car smart"]},{"name":"mdi:car-wrench","tags":["automotive","hardware / tools","car repair","mechanic"]},{"name":"mdi:caravan","tags":["transportation + road","home automation","automotive"]},{"name":"mdi:card","tags":["form","button"]},{"name":"mdi:card-account-details","tags":["account / user","identification card","user card details","id card","person card details","drivers license","business card"]},{"name":"mdi:card-account-details-outline","tags":["account / user","identification card outline","user card details outline","id card outline","person card details outline","drivers license outline","business card outline"]},{"name":"mdi:card-account-details-star","tags":["account / user","card account details favorite"]},{"name":"mdi:card-account-details-star-outline","tags":["account / user","card account details favorite outline"]},{"name":"mdi:card-bulleted","tags":[]},{"name":"mdi:card-bulleted-off","tags":[]},{"name":"mdi:card-bulleted-off-outline","tags":[]},{"name":"mdi:card-bulleted-outline","tags":[]},{"name":"mdi:card-bulleted-settings","tags":["settings"]},{"name":"mdi:card-bulleted-settings-outline","tags":["settings"]},{"name":"mdi:card-minus","tags":[]},{"name":"mdi:card-minus-outline","tags":[]},{"name":"mdi:card-multiple","tags":[]},{"name":"mdi:card-multiple-outline","tags":[]},{"name":"mdi:card-off","tags":[]},{"name":"mdi:card-off-outline","tags":[]},{"name":"mdi:card-outline","tags":["form","button outline"]},{"name":"mdi:card-plus","tags":[]},{"name":"mdi:card-plus-outline","tags":[]},{"name":"mdi:card-remove","tags":[]},{"name":"mdi:card-remove-outline","tags":[]},{"name":"mdi:card-text","tags":[]},{"name":"mdi:card-text-outline","tags":[]},{"name":"mdi:cards","tags":["gaming / rpg"]},{"name":"mdi:cards-club","tags":["gaming / rpg","suit clubs","poker club"]},{"name":"mdi:cards-club-outline","tags":[]},{"name":"mdi:cards-diamond","tags":["gaming / rpg","transportation + road","suit diamonds","hov lane","high occupancy vehicle lane","carpool lane","poker diamond"]},{"name":"mdi:cards-diamond-outline","tags":["transportation + road","hov lane outline","high occupancy vehicle lane outline","carpool lane outline","poker diamond outline"]},{"name":"mdi:cards-heart","tags":["gaming / rpg","suit hearts","poker heart"]},{"name":"mdi:cards-heart-outline","tags":[]},{"name":"mdi:cards-outline","tags":["gaming / rpg"]},{"name":"mdi:cards-playing","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-club","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-club-multiple","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-club-multiple-outline","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-club-outline","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-diamond","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-diamond-multiple","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-diamond-multiple-outline","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-diamond-outline","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-heart","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-heart-multiple","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-heart-multiple-outline","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-heart-outline","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-outline","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-spade","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-spade-multiple","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-spade-multiple-outline","tags":["gaming / rpg"]},{"name":"mdi:cards-playing-spade-outline","tags":["gaming / rpg"]},{"name":"mdi:cards-spade","tags":["gaming / rpg","suit spades","poker spade"]},{"name":"mdi:cards-spade-outline","tags":["gaming / rpg"]},{"name":"mdi:cards-variant","tags":["gaming / rpg"]},{"name":"mdi:carrot","tags":["agriculture","food / drink"]},{"name":"mdi:cart-arrow-down","tags":["shopping","shopping cart arrow down","trolley arrow down"]},{"name":"mdi:cart-arrow-right","tags":["shopping","trolley arrow right","shopping cart arrow right"]},{"name":"mdi:cart-arrow-up","tags":["shopping","shopping cart arrow up","trolley arrow up"]},{"name":"mdi:cart-check","tags":["shopping"]},{"name":"mdi:cart-heart","tags":["shopping","cart favorite","shopping favorite"]},{"name":"mdi:cart-minus","tags":["shopping","shopping cart minus","trolley minus"]},{"name":"mdi:cart-percent","tags":["shopping","cart discount","cart sale","trolley percent"]},{"name":"mdi:cart-remove","tags":["shopping","trolley remove","shopping cart remove"]},{"name":"mdi:cart-variant","tags":["shopping"]},{"name":"mdi:case-sensitive-alt","tags":[]},{"name":"mdi:cash","tags":["currency","banking","shopping","money"]},{"name":"mdi:cash-check","tags":["currency","banking"]},{"name":"mdi:cash-clock","tags":["banking","currency","date / time","cash schedule","payment schedule","payment clock","auto pay"]},{"name":"mdi:cash-fast","tags":["currency","banking","instant deposit","instant transfer","instant cash"]},{"name":"mdi:cash-lock","tags":["lock","currency","banking"]},{"name":"mdi:cash-lock-open","tags":["lock","currency","banking"]},{"name":"mdi:cash-marker","tags":["banking","currency","navigation","cod","cash on delivery","cash location"]},{"name":"mdi:cash-minus","tags":["currency","banking"]},{"name":"mdi:cash-multiple","tags":["currency","banking","money"]},{"name":"mdi:cash-off","tags":["currency","banking"]},{"name":"mdi:cash-plus","tags":["currency","banking"]},{"name":"mdi:cash-refund","tags":["banking","currency","cash return","cash chargeback"]},{"name":"mdi:cash-register","tags":["shopping","banking","till"]},{"name":"mdi:cash-remove","tags":["currency","banking"]},{"name":"mdi:cash-sync","tags":["banking","currency","auto pay","recurring payment","scheduled payment","cash cycle"]},{"name":"mdi:cassette","tags":["music","tape"]},{"name":"mdi:cast-audio","tags":["audio","cast speaker"]},{"name":"mdi:cast-audio-variant","tags":["apple airplay"]},{"name":"mdi:cast-off","tags":["home automation"]},{"name":"mdi:castle","tags":["places"]},{"name":"mdi:cat","tags":["animal","holiday","emoji cat","emoticon cat"]},{"name":"mdi:cctv","tags":["home automation","closed circuit television","security camera"]},{"name":"mdi:cctv-off","tags":["home automation","closed circuit television off","security camera off"]},{"name":"mdi:ceiling-fan","tags":["home automation"]},{"name":"mdi:ceiling-fan-light","tags":["home automation","ceiling fan on"]},{"name":"mdi:ceiling-light","tags":["home automation","ceiling lamp"]},{"name":"mdi:ceiling-light-multiple","tags":["home automation","ceiling lamp multiple"]},{"name":"mdi:ceiling-light-multiple-outline","tags":["home automation","ceiling lamp multiple outline"]},{"name":"mdi:ceiling-light-outline","tags":["home automation"]},{"name":"mdi:cellphone-arrow-down-variant","tags":["cellphone / phone","cellphone download"]},{"name":"mdi:cellphone-basic","tags":["cellphone / phone","device / tech","mobile phone basic"]},{"name":"mdi:cellphone-charging","tags":["cellphone / phone"]},{"name":"mdi:cellphone-check","tags":["cellphone / phone"]},{"name":"mdi:cellphone-key","tags":["cellphone / phone","device / tech","mobile phone key","smartphone key"]},{"name":"mdi:cellphone-marker","tags":["cellphone / phone","navigation","cellphone location","cellphone map","find my phone","cellphone gps"]},{"name":"mdi:cellphone-message","tags":["cellphone / phone","device / tech","mobile phone message","smartphone message"]},{"name":"mdi:cellphone-message-off","tags":["cellphone / phone"]},{"name":"mdi:cellphone-nfc-off","tags":["cellphone / phone"]},{"name":"mdi:cellphone-remove","tags":["cellphone / phone","device / tech","phonelink erase","mobile phone erase","smartphone erase","cellphone erase"]},{"name":"mdi:cellphone-text","tags":["cellphone / phone","device / tech","mobile phone text","smartphone text"]},{"name":"mdi:cellphone-wireless","tags":["cellphone / phone","device / tech","mobile phone wireless","smartphone wireless"]},{"name":"mdi:certificate","tags":["diploma","seal"]},{"name":"mdi:certificate-outline","tags":["diploma outline","seal outline"]},{"name":"mdi:chair-rolling","tags":["home automation","office chair","study chair"]},{"name":"mdi:chair-school","tags":["desk","education","learn"]},{"name":"mdi:chandelier","tags":["home automation","ceiling light","girandole","candelabra lamp","suspended light"]},{"name":"mdi:chart-arc","tags":["math","report arc","widget arc"]},{"name":"mdi:chart-areaspline","tags":["math","report areaspline","widget areaspline","graph areaspline"]},{"name":"mdi:chart-areaspline-variant","tags":["math","report areaspline variant","widget areaspline variant","graph areaspline variant"]},{"name":"mdi:chart-bar","tags":["math","report bar","widget bar","graph bar"]},{"name":"mdi:chart-bar-stacked","tags":["math","report bar stacked","widget bar stacked","graph bar stacked"]},{"name":"mdi:chart-bell-curve","tags":["math","report bell curve","widget bell curve","graph bell curve"]},{"name":"mdi:chart-bell-curve-cumulative","tags":["math","report bell curve cumulative","widget bell curve cumulative","graph bell curve cumulative"]},{"name":"mdi:chart-donut-variant","tags":["math","chart doughnut variant","report donut variant","widget donut variant"]},{"name":"mdi:chart-gantt","tags":["math","report gantt","timeline","widget gantt","roadmap"]},{"name":"mdi:chart-histogram","tags":["math","report histogram","widget histogram","graph histogram"]},{"name":"mdi:chart-line","tags":["math","report line","widget line","graph line"]},{"name":"mdi:chart-line-stacked","tags":["math","report line stacked","widget line stacked","graph line stacked"]},{"name":"mdi:chart-multiple","tags":["math","report multiple","widget multiple","graph multiple"]},{"name":"mdi:chart-ppf","tags":["math","chart production possibility frontier","report ppf","widget ppf","graph ppf"]},{"name":"mdi:chart-sankey","tags":["math","chart snakey","report sankey","widget sankey","graph sankey"]},{"name":"mdi:chart-sankey-variant","tags":["math","chart snakey variant","report sankey variant","widget sankey variant","graph sankey variant"]},{"name":"mdi:chart-scatter-plot","tags":["math","report scatter plot","widget scatter plot","graph scatter plot"]},{"name":"mdi:chart-scatter-plot-hexbin","tags":["math","chart scatterplot hexbin","report scatter plot hexbin","widget scatter plot hexbin","graph scatter plot hexbin"]},{"name":"mdi:chart-timeline","tags":["math","report timeline","widget timeline","graph timeline","roadmap"]},{"name":"mdi:chart-waterfall","tags":["math"]},{"name":"mdi:chat","tags":[]},{"name":"mdi:chat-alert","tags":["alert / error","chat warning"]},{"name":"mdi:chat-alert-outline","tags":["alert / error"]},{"name":"mdi:chat-minus","tags":[]},{"name":"mdi:chat-minus-outline","tags":[]},{"name":"mdi:chat-outline","tags":[]},{"name":"mdi:chat-plus","tags":[]},{"name":"mdi:chat-plus-outline","tags":[]},{"name":"mdi:chat-processing","tags":["chat typing"]},{"name":"mdi:chat-processing-outline","tags":["chat typing outline"]},{"name":"mdi:chat-question","tags":["chat help"]},{"name":"mdi:chat-question-outline","tags":["chat help outline"]},{"name":"mdi:chat-remove","tags":[]},{"name":"mdi:chat-remove-outline","tags":[]},{"name":"mdi:chat-sleep","tags":[]},{"name":"mdi:chat-sleep-outline","tags":[]},{"name":"mdi:check-bold","tags":["check thick","success thick","success bold"]},{"name":"mdi:check-decagram","tags":["verified","decagram check","approve","approval","tick decagram"]},{"name":"mdi:check-decagram-outline","tags":["approve","approval","verified"]},{"name":"mdi:check-network","tags":["tick network"]},{"name":"mdi:check-network-outline","tags":["tick network outline"]},{"name":"mdi:check-underline","tags":[]},{"name":"mdi:check-underline-circle","tags":[]},{"name":"mdi:check-underline-circle-outline","tags":[]},{"name":"mdi:checkbook-arrow-left","tags":["banking","chequebook arrow left"]},{"name":"mdi:checkbook-arrow-right","tags":["banking","chequebook arrow right"]},{"name":"mdi:checkbox-blank-badge","tags":["notification","form","checkbox blank notification","app notification","app badge"]},{"name":"mdi:checkbox-blank-badge-outline","tags":["notification","form","checkbox blank notification outline","app notification outline","app badge outline"]},{"name":"mdi:checkbox-blank-off","tags":["form"]},{"name":"mdi:checkbox-blank-off-outline","tags":["form"]},{"name":"mdi:checkbox-intermediate","tags":["form","checkbox indeterminate"]},{"name":"mdi:checkbox-intermediate-variant","tags":["form","checkbox indeterminate variant"]},{"name":"mdi:checkbox-marked-circle-auto-outline","tags":["form","task auto","todo auto"]},{"name":"mdi:checkbox-marked-circle-minus-outline","tags":["form","todo minus","task minus"]},{"name":"mdi:checkbox-marked-circle-plus-outline","tags":["form","task plus","task add","todo plus","todo add"]},{"name":"mdi:checkbox-multiple-blank","tags":["form","checkboxes blank"]},{"name":"mdi:checkbox-multiple-blank-circle","tags":["form","checkboxes blank circle"]},{"name":"mdi:checkbox-multiple-blank-circle-outline","tags":["form","checkboxes blank circle outline"]},{"name":"mdi:checkbox-multiple-blank-outline","tags":["form","checkboxes blank outline"]},{"name":"mdi:checkbox-multiple-marked","tags":["form","checkboxes marked"]},{"name":"mdi:checkbox-multiple-marked-circle","tags":["form","checkboxes marked circle"]},{"name":"mdi:checkbox-multiple-marked-circle-outline","tags":["form","checkboxes marked circle outline"]},{"name":"mdi:checkbox-multiple-marked-outline","tags":["form","checkboxes marked outline"]},{"name":"mdi:checkbox-multiple-outline","tags":["form","check boxes outline","tick box multiple outline"]},{"name":"mdi:checkbox-outline","tags":["form"]},{"name":"mdi:checkerboard","tags":["gaming / rpg","geographic information system","raster"]},{"name":"mdi:checkerboard-minus","tags":["geographic information system","raster minus"]},{"name":"mdi:checkerboard-plus","tags":["geographic information system","raster plus"]},{"name":"mdi:checkerboard-remove","tags":["geographic information system","raster remove"]},{"name":"mdi:cheese","tags":["food / drink","swiss cheese"]},{"name":"mdi:cheese-off","tags":["food / drink"]},{"name":"mdi:chef-hat","tags":["clothing","toque","cook"]},{"name":"mdi:chemical-weapon","tags":[]},{"name":"mdi:chess-bishop","tags":["gaming / rpg"]},{"name":"mdi:chess-king","tags":["gaming / rpg","crown","royalty"]},{"name":"mdi:chess-knight","tags":["gaming / rpg","chess horse"]},{"name":"mdi:chess-pawn","tags":["gaming / rpg"]},{"name":"mdi:chess-queen","tags":["gaming / rpg","crown","royalty"]},{"name":"mdi:chess-rook","tags":["gaming / rpg","chess castle","chess tower"]},{"name":"mdi:chevron-double-down","tags":["arrow"]},{"name":"mdi:chevron-double-left","tags":["arrow"]},{"name":"mdi:chevron-double-right","tags":["arrow"]},{"name":"mdi:chevron-double-up","tags":["arrow"]},{"name":"mdi:chevron-down-box","tags":["form","arrow"]},{"name":"mdi:chevron-down-box-outline","tags":["form","arrow"]},{"name":"mdi:chevron-down-circle","tags":["arrow"]},{"name":"mdi:chevron-down-circle-outline","tags":["arrow"]},{"name":"mdi:chevron-left-box","tags":["arrow"]},{"name":"mdi:chevron-left-box-outline","tags":["arrow"]},{"name":"mdi:chevron-left-circle","tags":["arrow"]},{"name":"mdi:chevron-left-circle-outline","tags":["arrow"]},{"name":"mdi:chevron-right-box","tags":["arrow"]},{"name":"mdi:chevron-right-box-outline","tags":["arrow"]},{"name":"mdi:chevron-right-circle","tags":["arrow"]},{"name":"mdi:chevron-right-circle-outline","tags":["arrow"]},{"name":"mdi:chevron-up-box","tags":["arrow"]},{"name":"mdi:chevron-up-box-outline","tags":["arrow"]},{"name":"mdi:chevron-up-circle","tags":["arrow"]},{"name":"mdi:chevron-up-circle-outline","tags":["arrow"]},{"name":"mdi:chili-alert","tags":["alert / error"]},{"name":"mdi:chili-alert-outline","tags":["alert / error"]},{"name":"mdi:chili-hot","tags":["food / drink","chilli hot","pepper","spicy"]},{"name":"mdi:chili-hot-outline","tags":[]},{"name":"mdi:chili-medium","tags":["food / drink","chilli medium","pepper","spicy"]},{"name":"mdi:chili-medium-outline","tags":[]},{"name":"mdi:chili-mild","tags":["food / drink","agriculture","chilli mild","pepper","spicy"]},{"name":"mdi:chili-mild-outline","tags":[]},{"name":"mdi:chili-off","tags":["food / drink","chilli off","pepper off","spicy off"]},{"name":"mdi:chili-off-outline","tags":[]},{"name":"mdi:chip","tags":["integrated circuit"]},{"name":"mdi:cigar","tags":[]},{"name":"mdi:cigar-off","tags":[]},{"name":"mdi:circle","tags":["shape","lens"]},{"name":"mdi:circle-box","tags":[]},{"name":"mdi:circle-box-outline","tags":[]},{"name":"mdi:circle-double","tags":["shape"]},{"name":"mdi:circle-half","tags":["shape","brightness half"]},{"name":"mdi:circle-half-full","tags":["shape"]},{"name":"mdi:circle-medium","tags":[]},{"name":"mdi:circle-multiple","tags":["currency","banking","coins"]},{"name":"mdi:circle-off-outline","tags":["null off"]},{"name":"mdi:circle-opacity","tags":["shape","drawing / art","circle transparent"]},{"name":"mdi:circle-outline","tags":["shape","null"]},{"name":"mdi:circle-slice-1","tags":[]},{"name":"mdi:circle-slice-2","tags":[]},{"name":"mdi:circle-slice-3","tags":[]},{"name":"mdi:circle-slice-4","tags":[]},{"name":"mdi:circle-slice-5","tags":[]},{"name":"mdi:circle-slice-6","tags":[]},{"name":"mdi:circle-slice-7","tags":[]},{"name":"mdi:circle-slice-8","tags":[]},{"name":"mdi:circle-small","tags":["math","bullet","multiplication","dot"]},{"name":"mdi:circular-saw","tags":["hardware / tools"]},{"name":"mdi:city-switch","tags":["places","city swap"]},{"name":"mdi:city-variant","tags":["places"]},{"name":"mdi:city-variant-outline","tags":["places"]},{"name":"mdi:clipboard","tags":[]},{"name":"mdi:clipboard-account-outline","tags":["account / user","clipboard user outline","clipboard person outline","assignment ind outline"]},{"name":"mdi:clipboard-alert-outline","tags":["alert / error","clipboard warning outline"]},{"name":"mdi:clipboard-arrow-left-outline","tags":[]},{"name":"mdi:clipboard-arrow-right","tags":[]},{"name":"mdi:clipboard-arrow-right-outline","tags":[]},{"name":"mdi:clipboard-arrow-up","tags":["clipboard arrow top"]},{"name":"mdi:clipboard-arrow-up-outline","tags":["clipboard arrow top outline"]},{"name":"mdi:clipboard-check-multiple","tags":[]},{"name":"mdi:clipboard-check-multiple-outline","tags":[]},{"name":"mdi:clipboard-check-outline","tags":["clipboard tick outline"]},{"name":"mdi:clipboard-clock","tags":["date / time"]},{"name":"mdi:clipboard-clock-outline","tags":["date / time"]},{"name":"mdi:clipboard-edit","tags":["edit / modify"]},{"name":"mdi:clipboard-edit-outline","tags":["edit / modify"]},{"name":"mdi:clipboard-file","tags":["files / folders"]},{"name":"mdi:clipboard-file-outline","tags":["files / folders"]},{"name":"mdi:clipboard-flow","tags":[]},{"name":"mdi:clipboard-flow-outline","tags":[]},{"name":"mdi:clipboard-list","tags":[]},{"name":"mdi:clipboard-list-outline","tags":[]},{"name":"mdi:clipboard-minus","tags":[]},{"name":"mdi:clipboard-minus-outline","tags":[]},{"name":"mdi:clipboard-multiple","tags":[]},{"name":"mdi:clipboard-multiple-outline","tags":[]},{"name":"mdi:clipboard-off","tags":[]},{"name":"mdi:clipboard-off-outline","tags":[]},{"name":"mdi:clipboard-outline","tags":[]},{"name":"mdi:clipboard-play","tags":[]},{"name":"mdi:clipboard-play-multiple","tags":[]},{"name":"mdi:clipboard-play-multiple-outline","tags":[]},{"name":"mdi:clipboard-play-outline","tags":[]},{"name":"mdi:clipboard-plus","tags":["clipboard add"]},{"name":"mdi:clipboard-plus-outline","tags":[]},{"name":"mdi:clipboard-pulse","tags":["medical / hospital","clipboard vitals"]},{"name":"mdi:clipboard-pulse-outline","tags":["medical / hospital","clipboard vitals outline"]},{"name":"mdi:clipboard-remove","tags":[]},{"name":"mdi:clipboard-remove-outline","tags":[]},{"name":"mdi:clipboard-search","tags":[]},{"name":"mdi:clipboard-search-outline","tags":[]},{"name":"mdi:clipboard-text-clock","tags":["date / time","clipboard text date","clipboard text time","clipboard text history"]},{"name":"mdi:clipboard-text-clock-outline","tags":["date / time","clipboard text date outline","clipboard text time outline","clipboard text history outline"]},{"name":"mdi:clipboard-text-multiple","tags":[]},{"name":"mdi:clipboard-text-multiple-outline","tags":[]},{"name":"mdi:clipboard-text-off","tags":[]},{"name":"mdi:clipboard-text-off-outline","tags":[]},{"name":"mdi:clipboard-text-outline","tags":[]},{"name":"mdi:clipboard-text-play","tags":[]},{"name":"mdi:clipboard-text-play-outline","tags":[]},{"name":"mdi:clipboard-text-search","tags":[]},{"name":"mdi:clipboard-text-search-outline","tags":[]},{"name":"mdi:clippy","tags":[]},{"name":"mdi:clock-alert","tags":["date / time","alert / error","clock warning"]},{"name":"mdi:clock-alert-outline","tags":["date / time","alert / error","clock warning"]},{"name":"mdi:clock-check","tags":["date / time"]},{"name":"mdi:clock-check-outline","tags":["date / time"]},{"name":"mdi:clock-digital","tags":["date / time","home automation"]},{"name":"mdi:clock-edit","tags":["date / time","edit / modify"]},{"name":"mdi:clock-edit-outline","tags":["date / time","edit / modify"]},{"name":"mdi:clock-end","tags":["date / time"]},{"name":"mdi:clock-fast","tags":["date / time","velocity"]},{"name":"mdi:clock-in","tags":["date / time"]},{"name":"mdi:clock-minus","tags":["date / time"]},{"name":"mdi:clock-minus-outline","tags":["date / time"]},{"name":"mdi:clock-out","tags":["date / time"]},{"name":"mdi:clock-plus","tags":["date / time"]},{"name":"mdi:clock-plus-outline","tags":["date / time"]},{"name":"mdi:clock-remove","tags":["date / time"]},{"name":"mdi:clock-remove-outline","tags":["date / time"]},{"name":"mdi:clock-star-four-points","tags":["date / time","clock auto"]},{"name":"mdi:clock-star-four-points-outline","tags":["date / time","clock auto outline"]},{"name":"mdi:clock-start","tags":["date / time"]},{"name":"mdi:clock-time-eight","tags":["date / time"]},{"name":"mdi:clock-time-eight-outline","tags":["date / time"]},{"name":"mdi:clock-time-eleven","tags":["date / time"]},{"name":"mdi:clock-time-eleven-outline","tags":["date / time"]},{"name":"mdi:clock-time-five","tags":["date / time"]},{"name":"mdi:clock-time-five-outline","tags":["date / time"]},{"name":"mdi:clock-time-four","tags":["date / time"]},{"name":"mdi:clock-time-four-outline","tags":["date / time"]},{"name":"mdi:clock-time-nine","tags":["date / time"]},{"name":"mdi:clock-time-nine-outline","tags":["date / time"]},{"name":"mdi:clock-time-one","tags":["date / time"]},{"name":"mdi:clock-time-one-outline","tags":["date / time"]},{"name":"mdi:clock-time-seven","tags":["date / time"]},{"name":"mdi:clock-time-seven-outline","tags":["date / time"]},{"name":"mdi:clock-time-six","tags":["date / time"]},{"name":"mdi:clock-time-six-outline","tags":["date / time"]},{"name":"mdi:clock-time-ten","tags":["date / time"]},{"name":"mdi:clock-time-ten-outline","tags":["date / time"]},{"name":"mdi:clock-time-three","tags":["date / time"]},{"name":"mdi:clock-time-three-outline","tags":["date / time"]},{"name":"mdi:clock-time-twelve","tags":["date / time"]},{"name":"mdi:clock-time-twelve-outline","tags":["date / time"]},{"name":"mdi:clock-time-two","tags":["date / time"]},{"name":"mdi:clock-time-two-outline","tags":["date / time"]},{"name":"mdi:close-box","tags":["math","form","multiply box","clear box","cancel box","remove box"]},{"name":"mdi:close-box-multiple","tags":["form","close boxes","library remove","library close","multiply boxes","multiply box multiple","cancel box multiple","remove box multiple"]},{"name":"mdi:close-box-multiple-outline","tags":["form","close boxes outline","library remove outline","library close outline","multiply boxes outline","multiply box multiple outline","remove box multiple","cancel box multiple"]},{"name":"mdi:close-box-outline","tags":["math","form","multiply box outline","clear box outline","remove box outline","cancel box outline"]},{"name":"mdi:close-circle","tags":["form","remove circle","cancel circle","multiply circle","clear circle"]},{"name":"mdi:close-circle-multiple","tags":["form","remove circle multiple","coins close","coins remove","clear circle multiple","multiply circle multiple"]},{"name":"mdi:close-circle-multiple-outline","tags":["form","remove circle multiple outline","coins close outline","coins remove outline","cancel circle multiple outline","multiply circle multiple outline","clear circle multiple outline"]},{"name":"mdi:close-network","tags":["remove network","cancel network","multiply network","clear network"]},{"name":"mdi:close-network-outline","tags":["remove network outline","cancel network outline","multiply network outline","clear network outline"]},{"name":"mdi:close-octagon","tags":["dangerous","multiply octagon","remove octagon","cancel octagon","clear octagon","stop remove"]},{"name":"mdi:close-octagon-outline","tags":["remove octagon outline","multiply octagon outline","clear octagon outline","cancel octagon outline","stop remove outline"]},{"name":"mdi:close-outline","tags":["remove outline","cancel outline","multiply outline","clear outline"]},{"name":"mdi:close-thick","tags":["close bold","remove thick","remove bold","multiply thick","multiply bold","clear thick","clear bold","cancel thick","cancel bold"]},{"name":"mdi:cloud-alert","tags":["alert / error","cloud","weather","cloud warning"]},{"name":"mdi:cloud-alert-outline","tags":["alert / error","weather","cloud"]},{"name":"mdi:cloud-arrow-down","tags":["cloud","weather"]},{"name":"mdi:cloud-arrow-down-outline","tags":["cloud","weather"]},{"name":"mdi:cloud-arrow-left","tags":["weather","cloud"]},{"name":"mdi:cloud-arrow-left-outline","tags":["weather","cloud"]},{"name":"mdi:cloud-arrow-right","tags":["weather","cloud"]},{"name":"mdi:cloud-arrow-right-outline","tags":["weather","cloud"]},{"name":"mdi:cloud-arrow-up","tags":["cloud","weather"]},{"name":"mdi:cloud-arrow-up-outline","tags":["weather","cloud"]},{"name":"mdi:cloud-braces","tags":["cloud","developer / languages","cloud json"]},{"name":"mdi:cloud-cancel","tags":["cloud","weather"]},{"name":"mdi:cloud-cancel-outline","tags":["cloud","weather"]},{"name":"mdi:cloud-check","tags":["cloud","weather"]},{"name":"mdi:cloud-check-outline","tags":["cloud","weather"]},{"name":"mdi:cloud-check-variant-outline","tags":["cloud","weather"]},{"name":"mdi:cloud-clock","tags":["weather","cloud"]},{"name":"mdi:cloud-clock-outline","tags":["cloud","weather"]},{"name":"mdi:cloud-cog","tags":["cloud","weather"]},{"name":"mdi:cloud-cog-outline","tags":["cloud","weather"]},{"name":"mdi:cloud-lock","tags":["cloud","lock"]},{"name":"mdi:cloud-lock-open","tags":["cloud"]},{"name":"mdi:cloud-lock-open-outline","tags":["cloud"]},{"name":"mdi:cloud-lock-outline","tags":["cloud","lock"]},{"name":"mdi:cloud-minus","tags":["cloud"]},{"name":"mdi:cloud-minus-outline","tags":["cloud"]},{"name":"mdi:cloud-percent","tags":["weather","cloud","nature","humidity","rain chance","cloud discount"]},{"name":"mdi:cloud-percent-outline","tags":["weather","cloud","nature","cloud discount outline","humidity outline","rain chance outline"]},{"name":"mdi:cloud-plus","tags":["cloud"]},{"name":"mdi:cloud-plus-outline","tags":["cloud"]},{"name":"mdi:cloud-print","tags":["cloud","printer","home automation"]},{"name":"mdi:cloud-print-outline","tags":["cloud","printer","home automation"]},{"name":"mdi:cloud-question","tags":["cloud","weather"]},{"name":"mdi:cloud-question-outline","tags":["cloud","weather"]},{"name":"mdi:cloud-refresh","tags":["cloud","weather"]},{"name":"mdi:cloud-refresh-outline","tags":["cloud","weather"]},{"name":"mdi:cloud-refresh-variant","tags":["cloud","weather"]},{"name":"mdi:cloud-refresh-variant-outline","tags":["cloud","weather"]},{"name":"mdi:cloud-remove","tags":["cloud"]},{"name":"mdi:cloud-remove-outline","tags":["cloud","weather"]},{"name":"mdi:cloud-search","tags":["cloud","weather"]},{"name":"mdi:cloud-search-outline","tags":["cloud","weather"]},{"name":"mdi:cloud-sync","tags":["cloud","weather"]},{"name":"mdi:cloud-sync-outline","tags":["cloud","weather"]},{"name":"mdi:cloud-tags","tags":["cloud","cloud xml"]},{"name":"mdi:clouds","tags":["weather","cloud"]},{"name":"mdi:clover","tags":["nature","luck"]},{"name":"mdi:clover-outline","tags":["nature","luck outline"]},{"name":"mdi:coach-lamp","tags":["home automation","coach light","carriage lamp","carriage light"]},{"name":"mdi:coach-lamp-variant","tags":["home automation","coach light","carriage light","carriage lamp"]},{"name":"mdi:coat-rack","tags":["home automation","clothing","foyer","hallway","entry room"]},{"name":"mdi:code-array","tags":["developer / languages"]},{"name":"mdi:code-braces","tags":["developer / languages","math","set"]},{"name":"mdi:code-braces-box","tags":["developer / languages"]},{"name":"mdi:code-brackets","tags":["developer / languages","math","square brackets"]},{"name":"mdi:code-equal","tags":["developer / languages"]},{"name":"mdi:code-greater-than","tags":["developer / languages","math"]},{"name":"mdi:code-greater-than-or-equal","tags":["developer / languages","math"]},{"name":"mdi:code-json","tags":["developer / languages"]},{"name":"mdi:code-less-than","tags":["developer / languages","math"]},{"name":"mdi:code-less-than-or-equal","tags":["developer / languages","math"]},{"name":"mdi:code-not-equal","tags":["developer / languages"]},{"name":"mdi:code-not-equal-variant","tags":["developer / languages"]},{"name":"mdi:code-parentheses","tags":["developer / languages"]},{"name":"mdi:code-parentheses-box","tags":["developer / languages"]},{"name":"mdi:code-string","tags":["developer / languages"]},{"name":"mdi:code-tags-check","tags":["developer / languages","code tags tick"]},{"name":"mdi:coffee-maker-check","tags":["home automation","food / drink","coffee maker done","coffee maker complete"]},{"name":"mdi:coffee-maker-check-outline","tags":["home automation","food / drink","coffee maker complete outline","coffee maker done outline"]},{"name":"mdi:coffee-off","tags":["food / drink","drink off","tea off","cup off","free breakfast off","local cafe off"]},{"name":"mdi:coffee-off-outline","tags":["food / drink","drink off outline","cup off outline","tea off outline","free breakfast off outline","local cafe off outline"]},{"name":"mdi:coffee-to-go","tags":["food / drink","tea to go","drink to go","cup to go","free breakfast to go","local cafe to go"]},{"name":"mdi:coffee-to-go-outline","tags":["food / drink","tea to go outline","cup to go outline","drink to go outline","free breakfast to go outline","local cafe to go outline"]},{"name":"mdi:coffin","tags":["holiday","death","dead"]},{"name":"mdi:cog-clockwise","tags":["settings"]},{"name":"mdi:cog-counterclockwise","tags":["settings"]},{"name":"mdi:cog-off","tags":["settings","settings off"]},{"name":"mdi:cog-off-outline","tags":["settings","settings off outline"]},{"name":"mdi:cog-pause","tags":["settings","settings pause","gear pause"]},{"name":"mdi:cog-pause-outline","tags":["settings","settings pause outline","gear pause outline"]},{"name":"mdi:cog-play","tags":["settings","settings play","gear play"]},{"name":"mdi:cog-play-outline","tags":["settings","settings play outline","gear play outline"]},{"name":"mdi:cog-refresh","tags":["settings","settings refresh"]},{"name":"mdi:cog-refresh-outline","tags":["settings","settings refresh outline"]},{"name":"mdi:cog-stop","tags":["settings","settings stop","gear stop"]},{"name":"mdi:cog-stop-outline","tags":["settings","settings stop outline","gear stop outline"]},{"name":"mdi:cog-sync","tags":["settings","settings sync"]},{"name":"mdi:cog-sync-outline","tags":["settings","settings sync outline"]},{"name":"mdi:cog-transfer","tags":["settings","settings transfer"]},{"name":"mdi:cog-transfer-outline","tags":["settings","settings transfer outline"]},{"name":"mdi:collapse-all","tags":["animation minus"]},{"name":"mdi:collapse-all-outline","tags":["animation minus outline"]},{"name":"mdi:comma","tags":[]},{"name":"mdi:comma-box","tags":[]},{"name":"mdi:comma-box-outline","tags":[]},{"name":"mdi:comma-circle","tags":[]},{"name":"mdi:comma-circle-outline","tags":[]},{"name":"mdi:comment","tags":[]},{"name":"mdi:comment-account","tags":["account / user","comment user","comment person"]},{"name":"mdi:comment-account-outline","tags":["account / user","comment user outline","comment person outline"]},{"name":"mdi:comment-alert","tags":["alert / error","comment warning"]},{"name":"mdi:comment-alert-outline","tags":["alert / error","comment warning outline"]},{"name":"mdi:comment-arrow-left","tags":["comment previous"]},{"name":"mdi:comment-arrow-left-outline","tags":["comment previous outline"]},{"name":"mdi:comment-arrow-right","tags":["comment next"]},{"name":"mdi:comment-arrow-right-outline","tags":["comment next outline"]},{"name":"mdi:comment-bookmark","tags":[]},{"name":"mdi:comment-bookmark-outline","tags":[]},{"name":"mdi:comment-check","tags":["comment tick"]},{"name":"mdi:comment-check-outline","tags":["comment tick outline"]},{"name":"mdi:comment-edit","tags":["edit / modify"]},{"name":"mdi:comment-edit-outline","tags":["edit / modify"]},{"name":"mdi:comment-eye","tags":[]},{"name":"mdi:comment-eye-outline","tags":[]},{"name":"mdi:comment-flash","tags":["comment quick"]},{"name":"mdi:comment-flash-outline","tags":["comment quick outline"]},{"name":"mdi:comment-minus","tags":[]},{"name":"mdi:comment-minus-outline","tags":[]},{"name":"mdi:comment-multiple","tags":["comments"]},{"name":"mdi:comment-multiple-outline","tags":["comments outline"]},{"name":"mdi:comment-off","tags":[]},{"name":"mdi:comment-off-outline","tags":[]},{"name":"mdi:comment-outline","tags":[]},{"name":"mdi:comment-plus","tags":["comment add"]},{"name":"mdi:comment-plus-outline","tags":["comment add outline"]},{"name":"mdi:comment-processing","tags":[]},{"name":"mdi:comment-processing-outline","tags":[]},{"name":"mdi:comment-question","tags":["comment help"]},{"name":"mdi:comment-question-outline","tags":["comment help outline"]},{"name":"mdi:comment-quote","tags":["feedback"]},{"name":"mdi:comment-quote-outline","tags":["feedback outline"]},{"name":"mdi:comment-remove","tags":[]},{"name":"mdi:comment-remove-outline","tags":[]},{"name":"mdi:comment-search","tags":[]},{"name":"mdi:comment-search-outline","tags":[]},{"name":"mdi:comment-text","tags":[]},{"name":"mdi:comment-text-multiple","tags":["comments text"]},{"name":"mdi:comment-text-multiple-outline","tags":["comments text outline"]},{"name":"mdi:comment-text-outline","tags":[]},{"name":"mdi:compare-remove","tags":[]},{"name":"mdi:compass-outline","tags":["navigation","geographic information system"]},{"name":"mdi:compass-rose","tags":["navigation"]},{"name":"mdi:cone","tags":["shape"]},{"name":"mdi:cone-off","tags":["shape"]},{"name":"mdi:connection","tags":["home automation","plug"]},{"name":"mdi:console","tags":["terminal"]},{"name":"mdi:console-line","tags":["terminal line"]},{"name":"mdi:console-network","tags":["terminal network"]},{"name":"mdi:console-network-outline","tags":["terminal network outline"]},{"name":"mdi:consolidate","tags":[]},{"name":"mdi:contactless-payment","tags":["currency"]},{"name":"mdi:contactless-payment-circle-outline","tags":["currency"]},{"name":"mdi:contain","tags":[]},{"name":"mdi:contain-end","tags":[]},{"name":"mdi:contain-start","tags":[]},{"name":"mdi:content-duplicate","tags":[]},{"name":"mdi:content-save-alert","tags":["alert / error","floppy disc alert"]},{"name":"mdi:content-save-alert-outline","tags":["alert / error","floppy disc alert outline"]},{"name":"mdi:content-save-all","tags":["floppy disc multiple"]},{"name":"mdi:content-save-all-outline","tags":["floppy disc multiple outline"]},{"name":"mdi:content-save-check","tags":[]},{"name":"mdi:content-save-check-outline","tags":[]},{"name":"mdi:content-save-cog","tags":["settings","floppy disc cog"]},{"name":"mdi:content-save-cog-outline","tags":["settings","floppy disc cog outline"]},{"name":"mdi:content-save-edit","tags":["edit / modify","floppy disc edit"]},{"name":"mdi:content-save-edit-outline","tags":["edit / modify","floppy disc edit outline"]},{"name":"mdi:content-save-minus","tags":[]},{"name":"mdi:content-save-minus-outline","tags":[]},{"name":"mdi:content-save-move","tags":["floppy disc move"]},{"name":"mdi:content-save-move-outline","tags":["floppy disc move outline"]},{"name":"mdi:content-save-off","tags":[]},{"name":"mdi:content-save-off-outline","tags":[]},{"name":"mdi:content-save-plus","tags":["content save add"]},{"name":"mdi:content-save-plus-outline","tags":["content save add outline"]},{"name":"mdi:content-save-settings","tags":["settings","floppy disc settings"]},{"name":"mdi:content-save-settings-outline","tags":["settings","floppy disc settings outline"]},{"name":"mdi:contrast","tags":[]},{"name":"mdi:controller-classic","tags":["gaming / rpg","gamepad classic"]},{"name":"mdi:controller-classic-outline","tags":["gaming / rpg","gamepad classic outline"]},{"name":"mdi:cookie-alert","tags":["food / drink","alert / error","biscuit alert"]},{"name":"mdi:cookie-alert-outline","tags":["food / drink","alert / error","biscuit alert outline"]},{"name":"mdi:cookie-check","tags":["food / drink","biscuit check"]},{"name":"mdi:cookie-check-outline","tags":["food / drink","biscuit check outline"]},{"name":"mdi:cookie-clock","tags":["food / drink","date / time","biscuit clock"]},{"name":"mdi:cookie-clock-outline","tags":["food / drink","date / time","biscuit clock outline"]},{"name":"mdi:cookie-cog","tags":["food / drink","settings","biscuit cog"]},{"name":"mdi:cookie-cog-outline","tags":["food / drink","settings","biscuit cog outline"]},{"name":"mdi:cookie-edit","tags":["food / drink","edit / modify","biscuit edit"]},{"name":"mdi:cookie-edit-outline","tags":["food / drink","edit / modify","biscuit edit outline"]},{"name":"mdi:cookie-lock","tags":["food / drink","lock","biscuit lock"]},{"name":"mdi:cookie-lock-outline","tags":["food / drink","lock","biscuit lock outline"]},{"name":"mdi:cookie-minus","tags":["food / drink","biscuit minus"]},{"name":"mdi:cookie-minus-outline","tags":["food / drink","biscuit minus outline"]},{"name":"mdi:cookie-off","tags":["food / drink","biscuit off"]},{"name":"mdi:cookie-off-outline","tags":["food / drink","biscuit off outline"]},{"name":"mdi:cookie-outline","tags":["food / drink","biscuit outline"]},{"name":"mdi:cookie-plus","tags":["food / drink","biscuit plus"]},{"name":"mdi:cookie-plus-outline","tags":["food / drink","biscuit plus outline"]},{"name":"mdi:cookie-refresh","tags":["food / drink","biscuit refresh"]},{"name":"mdi:cookie-refresh-outline","tags":["food / drink","biscuit refresh outline"]},{"name":"mdi:cookie-remove","tags":["food / drink","biscuit remove"]},{"name":"mdi:cookie-remove-outline","tags":["food / drink","biscuit remove outline"]},{"name":"mdi:cookie-settings","tags":["food / drink","settings","biscuit settings","cookie crumbs","biscuit crumbs"]},{"name":"mdi:cookie-settings-outline","tags":["food / drink","settings","biscuit settings outline","cookie crumbs outline","biscuit crumbs outline"]},{"name":"mdi:coolant-temperature","tags":["automotive"]},{"name":"mdi:copyleft","tags":[]},{"name":"mdi:corn","tags":["agriculture","food / drink"]},{"name":"mdi:corn-off","tags":["food / drink","agriculture"]},{"name":"mdi:cosine-wave","tags":["audio","frequency","amplitude"]},{"name":"mdi:counter","tags":["automotive","score","numbers","odometer"]},{"name":"mdi:cow","tags":["animal","agriculture","emoji cow","emoticon cow"]},{"name":"mdi:cow-off","tags":["food / drink","agriculture","animal","dairy off","dairy free"]},{"name":"mdi:cpu-32-bit","tags":["chip 32 bit"]},{"name":"mdi:cpu-64-bit","tags":["chip 64 bit"]},{"name":"mdi:crane","tags":[]},{"name":"mdi:creation-outline","tags":["auto awesome outline"]},{"name":"mdi:credit-card","tags":["banking","currency"]},{"name":"mdi:credit-card-check","tags":["banking"]},{"name":"mdi:credit-card-check-outline","tags":["banking"]},{"name":"mdi:credit-card-chip","tags":["banking","credit card icc chip"]},{"name":"mdi:credit-card-chip-outline","tags":["banking","credit card icc chip outline"]},{"name":"mdi:credit-card-clock","tags":["banking","date / time"]},{"name":"mdi:credit-card-clock-outline","tags":["banking","date / time"]},{"name":"mdi:credit-card-edit","tags":["edit / modify","banking"]},{"name":"mdi:credit-card-edit-outline","tags":["edit / modify","banking"]},{"name":"mdi:credit-card-fast","tags":["banking","credit card swipe"]},{"name":"mdi:credit-card-fast-outline","tags":["banking","credit card swipe outline"]},{"name":"mdi:credit-card-lock","tags":["banking","lock"]},{"name":"mdi:credit-card-lock-outline","tags":["banking","lock"]},{"name":"mdi:credit-card-marker","tags":["banking","navigation","credit card location","payment on delivery"]},{"name":"mdi:credit-card-marker-outline","tags":["banking","navigation","cod","payment on delivery outline","credit card location outline"]},{"name":"mdi:credit-card-minus","tags":["banking"]},{"name":"mdi:credit-card-minus-outline","tags":["banking"]},{"name":"mdi:credit-card-multiple","tags":["banking"]},{"name":"mdi:credit-card-multiple-outline","tags":["banking","credit cards"]},{"name":"mdi:credit-card-off","tags":["banking"]},{"name":"mdi:credit-card-off-outline","tags":["banking"]},{"name":"mdi:credit-card-plus","tags":["banking"]},{"name":"mdi:credit-card-plus-outline","tags":["banking","credit card add"]},{"name":"mdi:credit-card-refresh","tags":["banking"]},{"name":"mdi:credit-card-refresh-outline","tags":["banking"]},{"name":"mdi:credit-card-refund","tags":["banking"]},{"name":"mdi:credit-card-refund-outline","tags":["banking"]},{"name":"mdi:credit-card-remove","tags":["banking"]},{"name":"mdi:credit-card-remove-outline","tags":["banking"]},{"name":"mdi:credit-card-scan","tags":["banking"]},{"name":"mdi:credit-card-scan-outline","tags":["banking"]},{"name":"mdi:credit-card-search","tags":["banking"]},{"name":"mdi:credit-card-search-outline","tags":["banking"]},{"name":"mdi:credit-card-settings","tags":["banking","settings"]},{"name":"mdi:credit-card-settings-outline","tags":["banking","settings","payment settings"]},{"name":"mdi:credit-card-sync","tags":["banking"]},{"name":"mdi:credit-card-sync-outline","tags":["banking"]},{"name":"mdi:credit-card-wireless","tags":["currency","banking"]},{"name":"mdi:credit-card-wireless-off","tags":["banking"]},{"name":"mdi:credit-card-wireless-off-outline","tags":["banking"]},{"name":"mdi:credit-card-wireless-outline","tags":["currency","banking","credit card contactless"]},{"name":"mdi:cross","tags":["religion","holiday","christianity","religion christian"]},{"name":"mdi:cross-bolnisi","tags":["religion"]},{"name":"mdi:cross-celtic","tags":["religion","holiday"]},{"name":"mdi:cross-outline","tags":["religion","religion christian outline","christianity outline"]},{"name":"mdi:crown","tags":[]},{"name":"mdi:crown-circle","tags":["gaming / rpg","checkers"]},{"name":"mdi:crown-circle-outline","tags":["gaming / rpg","checkers outline"]},{"name":"mdi:crown-outline","tags":[]},{"name":"mdi:crystal-ball","tags":["gaming / rpg"]},{"name":"mdi:cube","tags":["shape"]},{"name":"mdi:cube-off","tags":[]},{"name":"mdi:cube-off-outline","tags":["food / drink","sugar off","sugar cube off","sugar free"]},{"name":"mdi:cube-outline","tags":["shape","food / drink","sugar","sugar cube"]},{"name":"mdi:cube-send","tags":[]},{"name":"mdi:cube-unfolded","tags":[]},{"name":"mdi:cup","tags":["food / drink","glass","drink"]},{"name":"mdi:cup-off","tags":["food / drink","glass off","drink off"]},{"name":"mdi:cup-off-outline","tags":["food / drink","glass off outline","drink off outline"]},{"name":"mdi:cup-outline","tags":["food / drink","glass outline","drink outline","cup empty"]},{"name":"mdi:cupboard","tags":["home automation"]},{"name":"mdi:cupboard-outline","tags":["home automation"]},{"name":"mdi:cupcake","tags":["food / drink"]},{"name":"mdi:curling","tags":["sport"]},{"name":"mdi:currency-bdt","tags":["banking","currency","taka","bangladeshi taka"]},{"name":"mdi:currency-brl","tags":["banking","currency","brazilian real"]},{"name":"mdi:currency-eth","tags":["currency","banking","ethereum","xi"]},{"name":"mdi:currency-eur-off","tags":["currency","banking"]},{"name":"mdi:currency-ils","tags":["banking","currency"]},{"name":"mdi:currency-inr","tags":["currency","banking","rupee"]},{"name":"mdi:currency-krw","tags":["currency","banking","won"]},{"name":"mdi:currency-kzt","tags":["banking","currency","kazakhstani tenge"]},{"name":"mdi:currency-mnt","tags":["currency","banking","currency mongolian tugrug"]},{"name":"mdi:currency-ngn","tags":["currency","banking","naira"]},{"name":"mdi:currency-php","tags":["banking","currency","philippine peso"]},{"name":"mdi:currency-rial","tags":["currency","banking","currency riyal","currency irr","currency omr","currency yer","currency sar"]},{"name":"mdi:currency-sign","tags":["currency","banking","currency scarab"]},{"name":"mdi:currency-thb","tags":["banking","currency thai baht"]},{"name":"mdi:currency-twd","tags":["currency","banking","new taiwan dollar"]},{"name":"mdi:currency-uah","tags":["banking","currency hryvnia","currency ukraine"]},{"name":"mdi:current-ac","tags":["alternating current"]},{"name":"mdi:current-dc","tags":["battery","direct current"]},{"name":"mdi:cursor-default","tags":[]},{"name":"mdi:cursor-default-click","tags":[]},{"name":"mdi:cursor-default-click-outline","tags":[]},{"name":"mdi:cursor-default-gesture","tags":[]},{"name":"mdi:cursor-default-gesture-outline","tags":[]},{"name":"mdi:cursor-default-outline","tags":[]},{"name":"mdi:cursor-move","tags":[]},{"name":"mdi:cursor-pointer","tags":["cursor hand"]},{"name":"mdi:cursor-text","tags":[]},{"name":"mdi:curtains","tags":["home automation","drapes","window"]},{"name":"mdi:curtains-closed","tags":["home automation","drapes closed","window closed"]},{"name":"mdi:cylinder","tags":["shape"]},{"name":"mdi:cylinder-off","tags":["shape"]},{"name":"mdi:dance-ballroom","tags":["people / family","human dance ballroom"]},{"name":"mdi:dance-pole","tags":["sport","people / family","kho kho","human dance pole"]},{"name":"mdi:data-matrix","tags":[]},{"name":"mdi:data-matrix-edit","tags":["edit / modify"]},{"name":"mdi:data-matrix-minus","tags":[]},{"name":"mdi:data-matrix-plus","tags":[]},{"name":"mdi:data-matrix-remove","tags":[]},{"name":"mdi:data-matrix-scan","tags":[]},{"name":"mdi:database","tags":["geographic information system","database","storage"]},{"name":"mdi:database-alert","tags":["database","alert / error"]},{"name":"mdi:database-alert-outline","tags":["database","alert / error"]},{"name":"mdi:database-arrow-down","tags":["database"]},{"name":"mdi:database-arrow-down-outline","tags":["database"]},{"name":"mdi:database-arrow-left","tags":["database"]},{"name":"mdi:database-arrow-left-outline","tags":["database"]},{"name":"mdi:database-arrow-right","tags":["database"]},{"name":"mdi:database-arrow-right-outline","tags":["database"]},{"name":"mdi:database-arrow-up","tags":["database"]},{"name":"mdi:database-arrow-up-outline","tags":["database"]},{"name":"mdi:database-check","tags":["geographic information system","database","database tick"]},{"name":"mdi:database-check-outline","tags":["database"]},{"name":"mdi:database-clock","tags":["database","date / time"]},{"name":"mdi:database-clock-outline","tags":["database","date / time"]},{"name":"mdi:database-cog","tags":["database","settings"]},{"name":"mdi:database-cog-outline","tags":["database","settings"]},{"name":"mdi:database-edit","tags":["edit / modify","geographic information system","database"]},{"name":"mdi:database-edit-outline","tags":["database","edit / modify"]},{"name":"mdi:database-export","tags":["geographic information system","database"]},{"name":"mdi:database-export-outline","tags":["database"]},{"name":"mdi:database-eye","tags":["database","database view"]},{"name":"mdi:database-eye-off","tags":["database","database view off"]},{"name":"mdi:database-eye-off-outline","tags":["database","database view off outline"]},{"name":"mdi:database-eye-outline","tags":["database","database view outline"]},{"name":"mdi:database-import","tags":["geographic information system","database"]},{"name":"mdi:database-import-outline","tags":["database"]},{"name":"mdi:database-lock","tags":["lock","geographic information system","database"]},{"name":"mdi:database-lock-outline","tags":["database","lock"]},{"name":"mdi:database-marker","tags":["geographic information system","database","navigation","database location"]},{"name":"mdi:database-marker-outline","tags":["database","navigation","database location outline"]},{"name":"mdi:database-minus","tags":["geographic information system","database"]},{"name":"mdi:database-minus-outline","tags":["database"]},{"name":"mdi:database-off","tags":["database"]},{"name":"mdi:database-off-outline","tags":["database"]},{"name":"mdi:database-outline","tags":["database"]},{"name":"mdi:database-plus","tags":["geographic information system","database","database add"]},{"name":"mdi:database-plus-outline","tags":["database"]},{"name":"mdi:database-refresh","tags":["database"]},{"name":"mdi:database-refresh-outline","tags":["database"]},{"name":"mdi:database-remove","tags":["geographic information system","database"]},{"name":"mdi:database-remove-outline","tags":["database"]},{"name":"mdi:database-search","tags":["geographic information system","database","sql query"]},{"name":"mdi:database-search-outline","tags":["database"]},{"name":"mdi:database-settings","tags":["settings","geographic information system","database"]},{"name":"mdi:database-settings-outline","tags":["database","settings"]},{"name":"mdi:database-sync","tags":["geographic information system","database"]},{"name":"mdi:database-sync-outline","tags":["database"]},{"name":"mdi:death-star","tags":[]},{"name":"mdi:death-star-variant","tags":[]},{"name":"mdi:deathly-hallows","tags":["harry potter"]},{"name":"mdi:debug-step-into","tags":[]},{"name":"mdi:debug-step-out","tags":[]},{"name":"mdi:debug-step-over","tags":["skip","jump"]},{"name":"mdi:decagram","tags":["shape","starburst"]},{"name":"mdi:decagram-outline","tags":["shape","starburst outline"]},{"name":"mdi:decimal","tags":["math"]},{"name":"mdi:decimal-comma","tags":["math"]},{"name":"mdi:decimal-comma-decrease","tags":["math"]},{"name":"mdi:decimal-comma-increase","tags":["math"]},{"name":"mdi:decimal-decrease","tags":["math"]},{"name":"mdi:decimal-increase","tags":["math"]},{"name":"mdi:delete-alert","tags":["alert / error"]},{"name":"mdi:delete-alert-outline","tags":["alert / error"]},{"name":"mdi:delete-circle","tags":["trash circle","bin circle","garbage can circle","garbage circle","rubbish bin circle","rubbish circle","trash can circle"]},{"name":"mdi:delete-circle-outline","tags":["bin circle outline","garbage can circle outline","garbage circle outline","rubbish bin circle outline","rubbish circle outline","trash can circle outline","trash circle outline"]},{"name":"mdi:delete-clock","tags":["date / time"]},{"name":"mdi:delete-clock-outline","tags":["date / time"]},{"name":"mdi:delete-empty","tags":["trash empty","bin empty","rubbish empty","rubbish bin empty","trash can empty","garbage empty","garbage can empty"]},{"name":"mdi:delete-empty-outline","tags":[]},{"name":"mdi:delete-off","tags":[]},{"name":"mdi:delete-off-outline","tags":[]},{"name":"mdi:delete-variant","tags":["trash variant","bin variant","cup ice","drink ice"]},{"name":"mdi:desk","tags":[]},{"name":"mdi:desk-lamp","tags":["home automation"]},{"name":"mdi:desk-lamp-off","tags":["home automation"]},{"name":"mdi:desk-lamp-on","tags":["home automation"]},{"name":"mdi:deskphone","tags":["cellphone / phone","device / tech"]},{"name":"mdi:desktop-classic","tags":["device / tech","home automation","computer classic"]},{"name":"mdi:desktop-tower","tags":["device / tech","home automation"]},{"name":"mdi:desktop-tower-monitor","tags":["device / tech"]},{"name":"mdi:dharmachakra","tags":["religion","dharma wheel","religion buddhist","buddhism"]},{"name":"mdi:diabetes","tags":["medical / hospital","hand blood"]},{"name":"mdi:diameter","tags":["math","circle diameter","sphere diameter"]},{"name":"mdi:diameter-outline","tags":["math","circle diameter outline","sphere diameter outline"]},{"name":"mdi:diameter-variant","tags":["math","circle diameter variant","sphere diameter variant"]},{"name":"mdi:diamond","tags":[]},{"name":"mdi:diamond-outline","tags":[]},{"name":"mdi:diamond-stone","tags":["jewel"]},{"name":"mdi:dice-1","tags":["gaming / rpg","die 1","dice one"]},{"name":"mdi:dice-1-outline","tags":["gaming / rpg"]},{"name":"mdi:dice-2","tags":["gaming / rpg","die 2","dice two"]},{"name":"mdi:dice-2-outline","tags":["gaming / rpg"]},{"name":"mdi:dice-3","tags":["gaming / rpg","die 3","dice three"]},{"name":"mdi:dice-3-outline","tags":["gaming / rpg"]},{"name":"mdi:dice-4","tags":["gaming / rpg","die 4","dice four"]},{"name":"mdi:dice-4-outline","tags":["gaming / rpg"]},{"name":"mdi:dice-5","tags":["gaming / rpg","die 5","dice five"]},{"name":"mdi:dice-5-outline","tags":["gaming / rpg"]},{"name":"mdi:dice-6","tags":["gaming / rpg","die 6","dice six"]},{"name":"mdi:dice-6-outline","tags":["gaming / rpg"]},{"name":"mdi:dice-d10","tags":["gaming / rpg"]},{"name":"mdi:dice-d10-outline","tags":["gaming / rpg","die d10"]},{"name":"mdi:dice-d12","tags":["gaming / rpg"]},{"name":"mdi:dice-d12-outline","tags":["gaming / rpg"]},{"name":"mdi:dice-d20","tags":["gaming / rpg"]},{"name":"mdi:dice-d20-outline","tags":["gaming / rpg","die d20"]},{"name":"mdi:dice-d4","tags":["gaming / rpg"]},{"name":"mdi:dice-d4-outline","tags":["gaming / rpg","die d4"]},{"name":"mdi:dice-d6","tags":["gaming / rpg"]},{"name":"mdi:dice-d6-outline","tags":["gaming / rpg","die d6"]},{"name":"mdi:dice-d8","tags":["gaming / rpg"]},{"name":"mdi:dice-d8-outline","tags":["gaming / rpg","die d8"]},{"name":"mdi:dice-multiple","tags":["gaming / rpg","die multiple"]},{"name":"mdi:dice-multiple-outline","tags":["gaming / rpg"]},{"name":"mdi:dip-switch","tags":[]},{"name":"mdi:disc","tags":["music","cd rom","dvd"]},{"name":"mdi:disc-player","tags":["home automation","device / tech"]},{"name":"mdi:dishwasher-alert","tags":["home automation","alert / error"]},{"name":"mdi:dishwasher-off","tags":["home automation"]},{"name":"mdi:distribute-horizontal-center","tags":[]},{"name":"mdi:distribute-horizontal-left","tags":[]},{"name":"mdi:distribute-horizontal-right","tags":[]},{"name":"mdi:distribute-vertical-bottom","tags":[]},{"name":"mdi:distribute-vertical-center","tags":[]},{"name":"mdi:distribute-vertical-top","tags":[]},{"name":"mdi:diversify","tags":[]},{"name":"mdi:diving-flippers","tags":["sport"]},{"name":"mdi:diving-helmet","tags":[]},{"name":"mdi:diving-scuba-flag","tags":[]},{"name":"mdi:diving-scuba-mask","tags":["sport"]},{"name":"mdi:diving-scuba-tank","tags":[]},{"name":"mdi:diving-scuba-tank-multiple","tags":[]},{"name":"mdi:diving-snorkel","tags":["sport"]},{"name":"mdi:division","tags":["math","obelus"]},{"name":"mdi:division-box","tags":["math"]},{"name":"mdi:dna","tags":["science","helix"]},{"name":"mdi:dock-bottom","tags":[]},{"name":"mdi:dock-left","tags":[]},{"name":"mdi:dock-right","tags":[]},{"name":"mdi:dock-top","tags":[]},{"name":"mdi:dock-window","tags":[]},{"name":"mdi:doctor","tags":["medical / hospital"]},{"name":"mdi:dog","tags":["animal","emoji dog","emoticon dog"]},{"name":"mdi:dog-service","tags":["animal","guide dog","k9","canine"]},{"name":"mdi:dog-side","tags":["animal","k9","canine"]},{"name":"mdi:dog-side-off","tags":["animal"]},{"name":"mdi:dolly","tags":["hand truck","trolley"]},{"name":"mdi:dolphin","tags":["animal","porpoise"]},{"name":"mdi:domain-plus","tags":[]},{"name":"mdi:domain-remove","tags":[]},{"name":"mdi:domain-switch","tags":[]},{"name":"mdi:dome-light","tags":[]},{"name":"mdi:domino-mask","tags":["robber mask","zorro mask"]},{"name":"mdi:donkey","tags":["animal"]},{"name":"mdi:door","tags":["home automation"]},{"name":"mdi:door-closed","tags":["home automation"]},{"name":"mdi:door-closed-lock","tags":["home automation","lock"]},{"name":"mdi:door-open","tags":["home automation"]},{"name":"mdi:door-sliding-lock","tags":["home automation","lock","patio door lock","french door lock"]},{"name":"mdi:door-sliding-open","tags":["home automation","patio door open","french door open"]},{"name":"mdi:doorbell","tags":["home automation"]},{"name":"mdi:doorbell-video","tags":["home automation"]},{"name":"mdi:dots-circle","tags":["perimeter"]},{"name":"mdi:dots-grid","tags":[]},{"name":"mdi:dots-hexagon","tags":[]},{"name":"mdi:dots-horizontal-circle-outline","tags":["ellipsis horizontal circle outline","more circle outline","menu"]},{"name":"mdi:dots-square","tags":["perimeter"]},{"name":"mdi:dots-triangle","tags":[]},{"name":"mdi:dots-vertical-circle","tags":["ellipsis vertical circle","menu"]},{"name":"mdi:dots-vertical-circle-outline","tags":["ellipsis vertical circle outline","menu"]},{"name":"mdi:download-box","tags":[]},{"name":"mdi:download-box-outline","tags":[]},{"name":"mdi:download-circle","tags":[]},{"name":"mdi:download-circle-outline","tags":[]},{"name":"mdi:download-lock","tags":["lock"]},{"name":"mdi:download-lock-outline","tags":["lock"]},{"name":"mdi:download-multiple","tags":["downloads"]},{"name":"mdi:download-network","tags":[]},{"name":"mdi:download-network-outline","tags":[]},{"name":"mdi:download-off","tags":[]},{"name":"mdi:download-off-outline","tags":[]},{"name":"mdi:drag","tags":[]},{"name":"mdi:drag-horizontal","tags":[]},{"name":"mdi:drag-variant","tags":[]},{"name":"mdi:drag-vertical","tags":[]},{"name":"mdi:drag-vertical-variant","tags":[]},{"name":"mdi:drama-masks","tags":["comedy","tragedy","theatre"]},{"name":"mdi:draw","tags":["drawing / art","form","sign","signature"]},{"name":"mdi:draw-pen","tags":["form","drawing / art","sign","signature"]},{"name":"mdi:drawing","tags":["drawing / art","shape"]},{"name":"mdi:dresser","tags":["home automation"]},{"name":"mdi:dresser-outline","tags":["home automation"]},{"name":"mdi:drone","tags":["transportation + flying"]},{"name":"mdi:duck","tags":["animal"]},{"name":"mdi:dump-truck","tags":["transportation + road","hardware / tools","tipper lorry"]},{"name":"mdi:ear-hearing-loop","tags":["medical / hospital","audio induction loop","telecoil"]},{"name":"mdi:ear-hearing-off","tags":["medical / hospital","hearing impaired"]},{"name":"mdi:earbuds","tags":["audio","music","headphones"]},{"name":"mdi:earbuds-off","tags":["audio","music","headphones off"]},{"name":"mdi:earbuds-off-outline","tags":["audio","music","headphones off outline"]},{"name":"mdi:earbuds-outline","tags":["audio","music","headphones outline"]},{"name":"mdi:earth-arrow-right","tags":["navigation","globe arrow right","world arrow right","planet arrow right"]},{"name":"mdi:earth-box","tags":["navigation","globe box","world box","planet box"]},{"name":"mdi:earth-box-minus","tags":["navigation","globe box minus","world box minus","planet box minus"]},{"name":"mdi:earth-box-off","tags":["navigation","globe box off","world box off","planet box off"]},{"name":"mdi:earth-box-plus","tags":["navigation","globe box plus","world box plus","planet box plus"]},{"name":"mdi:earth-box-remove","tags":["navigation","globe box remove","world box remove","planet box remove"]},{"name":"mdi:earth-minus","tags":["navigation","globe minus","world minus","planet minus"]},{"name":"mdi:earth-off","tags":["geographic information system","navigation","globe off","world off","planet off"]},{"name":"mdi:earth-plus","tags":["navigation","globe plus","world plus","planet plus"]},{"name":"mdi:earth-remove","tags":["navigation","globe remove","world remove","planet remove"]},{"name":"mdi:egg","tags":["food / drink","agriculture"]},{"name":"mdi:egg-easter","tags":["holiday"]},{"name":"mdi:egg-fried","tags":["food / drink"]},{"name":"mdi:egg-off","tags":["food / drink","agriculture"]},{"name":"mdi:egg-off-outline","tags":["food / drink","agriculture"]},{"name":"mdi:egg-outline","tags":["food / drink","agriculture"]},{"name":"mdi:eiffel-tower","tags":["places","paris","france"]},{"name":"mdi:eight-track","tags":["music","8 track"]},{"name":"mdi:eject-circle","tags":[]},{"name":"mdi:eject-circle-outline","tags":[]},{"name":"mdi:electric-switch","tags":[]},{"name":"mdi:electric-switch-closed","tags":[]},{"name":"mdi:elephant","tags":["animal"]},{"name":"mdi:elevation-decline","tags":[]},{"name":"mdi:elevation-rise","tags":[]},{"name":"mdi:elevator","tags":["transportation + other"]},{"name":"mdi:elevator-down","tags":["transportation + other"]},{"name":"mdi:elevator-passenger-off","tags":["transportation + other"]},{"name":"mdi:elevator-passenger-off-outline","tags":["transportation + other"]},{"name":"mdi:elevator-up","tags":["transportation + other"]},{"name":"mdi:ellipse","tags":["shape"]},{"name":"mdi:ellipse-outline","tags":["shape"]},{"name":"mdi:email-alert","tags":["alert / error","email warning","envelope alert","envelope warning"]},{"name":"mdi:email-alert-outline","tags":["alert / error"]},{"name":"mdi:email-arrow-left","tags":["email receive"]},{"name":"mdi:email-arrow-left-outline","tags":["email receive outline"]},{"name":"mdi:email-arrow-right","tags":["email send"]},{"name":"mdi:email-arrow-right-outline","tags":["email arrow right outline"]},{"name":"mdi:email-box","tags":["envelope box"]},{"name":"mdi:email-check","tags":["email tick"]},{"name":"mdi:email-check-outline","tags":["email tick outline"]},{"name":"mdi:email-edit","tags":["edit / modify"]},{"name":"mdi:email-edit-outline","tags":["edit / modify"]},{"name":"mdi:email-fast","tags":["envelope fast","email quick","email sent","email send"]},{"name":"mdi:email-fast-outline","tags":["email send outline","email sent outline","envelope fast outline","email quick outline"]},{"name":"mdi:email-heart-outline","tags":["love letter","envelope heart outline","greeting card"]},{"name":"mdi:email-lock","tags":["lock","envelope secure","email secure","envelope lock"]},{"name":"mdi:email-lock-outline","tags":["lock","email secure outline"]},{"name":"mdi:email-minus","tags":[]},{"name":"mdi:email-minus-outline","tags":[]},{"name":"mdi:email-multiple","tags":[]},{"name":"mdi:email-multiple-outline","tags":[]},{"name":"mdi:email-newsletter","tags":[]},{"name":"mdi:email-off","tags":[]},{"name":"mdi:email-off-outline","tags":[]},{"name":"mdi:email-open","tags":["drafts","envelope open"]},{"name":"mdi:email-open-heart-outline","tags":["love letter open","greeting card open","envelope open heart outline"]},{"name":"mdi:email-open-multiple","tags":[]},{"name":"mdi:email-open-multiple-outline","tags":[]},{"name":"mdi:email-open-outline","tags":["envelope open outline"]},{"name":"mdi:email-plus","tags":["email add","envelope add","envelope plus"]},{"name":"mdi:email-plus-outline","tags":["email add outline","envelope add outline","envelope plus outline"]},{"name":"mdi:email-remove","tags":[]},{"name":"mdi:email-remove-outline","tags":[]},{"name":"mdi:email-seal","tags":["email certified","mail certified","mail seal","email verified","mail verified"]},{"name":"mdi:email-seal-outline","tags":["email verified outline","email certified outline","mail verified outline","mail certified outline","mail seal outline"]},{"name":"mdi:email-search","tags":[]},{"name":"mdi:email-search-outline","tags":[]},{"name":"mdi:email-sync","tags":["email refresh","email resend"]},{"name":"mdi:email-sync-outline","tags":["email refresh outline","email resend outline"]},{"name":"mdi:email-variant","tags":["envelope variant"]},{"name":"mdi:emoticon-angry","tags":["emoji","smiley angry","face angry","emoji angry"]},{"name":"mdi:emoticon-angry-outline","tags":["emoji","smiley angry outline","face angry outline","emoji angry outline"]},{"name":"mdi:emoticon-confused","tags":["emoji","face confused","emoji confused"]},{"name":"mdi:emoticon-confused-outline","tags":["emoji","face confused outline","emoji confused outline"]},{"name":"mdi:emoticon-cool","tags":["emoji","smiley cool","face cool","face sunglasses","emoji cool"]},{"name":"mdi:emoticon-cool-outline","tags":["emoji","smiley cool outline","face cool outline","face sunglasses outline","emoji cool outline"]},{"name":"mdi:emoticon-cry","tags":["emoji","smiley cry","face cry","emoji cry"]},{"name":"mdi:emoticon-cry-outline","tags":["emoji","smiley cry outline","face cry outline","emoji cry outline"]},{"name":"mdi:emoticon-devil","tags":["emoji","smiley devil","face devil","emoji devil"]},{"name":"mdi:emoticon-devil-outline","tags":["emoji","smiley devil outline","face devil outline","emoji devil outline"]},{"name":"mdi:emoticon-frown","tags":["emoji","face frown","emoji frown"]},{"name":"mdi:emoticon-happy","tags":["emoji","smiley happy","face happy","emoji happy"]},{"name":"mdi:emoticon-happy-outline","tags":["emoji","smiley happy outline","face happy outline","emoji happy outline"]},{"name":"mdi:emoticon-kiss","tags":["emoji","smiley kiss","face kiss","emoji kiss"]},{"name":"mdi:emoticon-kiss-outline","tags":["emoji","smiley kiss outline","face kiss outline","emoji kiss outline"]},{"name":"mdi:emoticon-lol","tags":["emoji","face lol","emoji lol"]},{"name":"mdi:emoticon-lol-outline","tags":["emoji","face lol outline","emoji lol outline"]},{"name":"mdi:emoticon-neutral","tags":["emoji","smiley neutral","face neutral","emoji neutral"]},{"name":"mdi:emoticon-neutral-outline","tags":["emoji","smiley neutral outline","face neutral outline","emoji neutral outline"]},{"name":"mdi:emoticon-poop","tags":["emoji","smiley poop","face poop","emoji poop"]},{"name":"mdi:emoticon-poop-outline","tags":["emoji","face poop outline","emoji poop outline"]},{"name":"mdi:emoticon-sad","tags":["emoji","smiley sad","face sad","emoji sad"]},{"name":"mdi:emoticon-sad-outline","tags":["emoji","smiley sad outline","face sad outline","emoji sad outline"]},{"name":"mdi:emoticon-tongue","tags":["emoji","smiley tongue","face tongue","emoji tongue"]},{"name":"mdi:emoticon-tongue-outline","tags":["emoji","smiley tongue outline","face tongue outline","emoji tongue outline"]},{"name":"mdi:emoticon-wink","tags":["emoji","smiley wink","face wink","emoji wink"]},{"name":"mdi:emoticon-wink-outline","tags":["emoji","smiley wink outline","face wink outline","emoji wink outline"]},{"name":"mdi:engine","tags":["automotive","motor"]},{"name":"mdi:engine-off","tags":["automotive","motor off"]},{"name":"mdi:engine-off-outline","tags":["automotive","motor off outline"]},{"name":"mdi:engine-outline","tags":["automotive","motor outline"]},{"name":"mdi:epsilon","tags":["alpha / numeric"]},{"name":"mdi:equal","tags":["math"]},{"name":"mdi:equal-box","tags":["math"]},{"name":"mdi:equalizer-outline","tags":["audio"]},{"name":"mdi:eraser","tags":[]},{"name":"mdi:escalator","tags":["transportation + other"]},{"name":"mdi:escalator-box","tags":[]},{"name":"mdi:escalator-down","tags":["transportation + other"]},{"name":"mdi:escalator-up","tags":["transportation + other"]},{"name":"mdi:et","tags":[]},{"name":"mdi:ethernet","tags":[]},{"name":"mdi:ethernet-cable","tags":[]},{"name":"mdi:ethernet-cable-off","tags":[]},{"name":"mdi:ev-plug-ccs1","tags":["automotive","ev plug ccs combo 1","ev charger ccs1"]},{"name":"mdi:ev-plug-ccs2","tags":["automotive","ev plug ccs combo 2","ev charger ccs2"]},{"name":"mdi:ev-plug-chademo","tags":["automotive","ev charger chademo"]},{"name":"mdi:ev-plug-tesla","tags":["automotive","ev charger tesla"]},{"name":"mdi:ev-plug-type1","tags":["automotive","ev plug j1772","ev charger type1"]},{"name":"mdi:ev-plug-type2","tags":["automotive","ev plug mennekes","ev charger type2"]},{"name":"mdi:excavator","tags":["hardware / tools"]},{"name":"mdi:exclamation","tags":["math","factorial"]},{"name":"mdi:exclamation-thick","tags":["exclamation bold"]},{"name":"mdi:exit-run","tags":["home automation","emergency exit"]},{"name":"mdi:expand-all","tags":["animation plus"]},{"name":"mdi:expand-all-outline","tags":["animation plus outline"]},{"name":"mdi:expansion-card","tags":["gaming / rpg","gpu","graphics processing unit","nic","network interface card"]},{"name":"mdi:expansion-card-variant","tags":["graphics processing unit","gpu","network interface card","nice"]},{"name":"mdi:exponent","tags":["math","power"]},{"name":"mdi:exponent-box","tags":["math","power box"]},{"name":"mdi:export","tags":["output"]},{"name":"mdi:eye-arrow-left","tags":["view arrow left"]},{"name":"mdi:eye-arrow-left-outline","tags":["view arrow left outline"]},{"name":"mdi:eye-arrow-right","tags":["view arrow right"]},{"name":"mdi:eye-arrow-right-outline","tags":["view arrow right outline"]},{"name":"mdi:eye-check","tags":["eye tick"]},{"name":"mdi:eye-check-outline","tags":["eye tick outline"]},{"name":"mdi:eye-circle","tags":[]},{"name":"mdi:eye-circle-outline","tags":[]},{"name":"mdi:eye-lock","tags":[]},{"name":"mdi:eye-lock-open","tags":[]},{"name":"mdi:eye-lock-open-outline","tags":[]},{"name":"mdi:eye-lock-outline","tags":[]},{"name":"mdi:eye-minus","tags":[]},{"name":"mdi:eye-minus-outline","tags":[]},{"name":"mdi:eye-off-outline","tags":["hide outline","visibility off outline"]},{"name":"mdi:eye-outline","tags":["show outline","visibility outline"]},{"name":"mdi:eye-plus","tags":["eye add"]},{"name":"mdi:eye-plus-outline","tags":["eye add outline"]},{"name":"mdi:eye-refresh","tags":["view refresh"]},{"name":"mdi:eye-refresh-outline","tags":["view refresh outline"]},{"name":"mdi:eye-remove","tags":[]},{"name":"mdi:eye-remove-outline","tags":[]},{"name":"mdi:eye-settings","tags":["settings"]},{"name":"mdi:eye-settings-outline","tags":["settings"]},{"name":"mdi:eyedropper","tags":["color","drawing / art","science","pipette"]},{"name":"mdi:eyedropper-minus","tags":["science"]},{"name":"mdi:eyedropper-off","tags":["science"]},{"name":"mdi:eyedropper-plus","tags":["science"]},{"name":"mdi:eyedropper-remove","tags":["science"]},{"name":"mdi:face-agent","tags":["customer service","support","emoji agent","emoticon agent"]},{"name":"mdi:face-man-shimmer-outline","tags":["people / family","photography","health / beauty","account / user","face retouching natural outline","face male shimmer outline","emoji man shimmer outline","emoticon man shimmer outline"]},{"name":"mdi:face-mask","tags":["medical / hospital","clothing"]},{"name":"mdi:face-mask-outline","tags":["medical / hospital","clothing"]},{"name":"mdi:face-recognition","tags":["photography","facial recognition","scan"]},{"name":"mdi:face-woman","tags":["people / family","face female","emoji woman","emoticon woman"]},{"name":"mdi:face-woman-outline","tags":["people / family","face female outline","emoji woman outline","emoticon woman outline"]},{"name":"mdi:face-woman-profile","tags":["people / family","face female profile","emoji woman profile","emoticon woman profile"]},{"name":"mdi:face-woman-shimmer","tags":["people / family","photography","health / beauty","account / user","face retouching natural woman","face female shimmer","emoji woman shimmer","emoticon woman shimmer"]},{"name":"mdi:face-woman-shimmer-outline","tags":["people / family","photography","health / beauty","account / user","face retouching natural woman outline","face female shimmer outline","emoji woman shimmer outline","emoticon woman shimmer outline"]},{"name":"mdi:factory","tags":["places","industrial"]},{"name":"mdi:family-tree","tags":["people / family"]},{"name":"mdi:fan","tags":["home automation","automotive"]},{"name":"mdi:fan-alert","tags":["home automation","alert / error"]},{"name":"mdi:fan-auto","tags":[]},{"name":"mdi:fan-chevron-down","tags":["home automation","fan speed down"]},{"name":"mdi:fan-chevron-up","tags":["home automation","fan speed up"]},{"name":"mdi:fan-clock","tags":["home automation","date / time","fan clock","fan schedule","fan timer"]},{"name":"mdi:fan-minus","tags":["home automation"]},{"name":"mdi:fan-off","tags":["home automation","automotive"]},{"name":"mdi:fan-plus","tags":["home automation"]},{"name":"mdi:fan-remove","tags":["home automation"]},{"name":"mdi:fan-speed-1","tags":["home automation","fan speed low"]},{"name":"mdi:fan-speed-2","tags":["home automation","fan speed medium"]},{"name":"mdi:fan-speed-3","tags":["home automation","fan speed high"]},{"name":"mdi:fast-forward-10","tags":[]},{"name":"mdi:fast-forward-15","tags":[]},{"name":"mdi:fast-forward-30","tags":[]},{"name":"mdi:fast-forward-45","tags":[]},{"name":"mdi:fast-forward-5","tags":[]},{"name":"mdi:fast-forward-60","tags":[]},{"name":"mdi:faucet","tags":["home automation","kitchen tap","bathroom tap","sink"]},{"name":"mdi:faucet-variant","tags":["home automation","bathroom tap","kitchen tap","sink"]},{"name":"mdi:feather","tags":["nature","quill"]},{"name":"mdi:feature-search","tags":["box","box search"]},{"name":"mdi:feature-search-outline","tags":["box","box outline","box search outline"]},{"name":"mdi:fence","tags":["home automation","agriculture","railway","train track"]},{"name":"mdi:fence-electric","tags":["home automation","agriculture","railway electric","train track electric"]},{"name":"mdi:file-account","tags":["account / user","files / folders","file user","resume"]},{"name":"mdi:file-account-outline","tags":["files / folders","account / user"]},{"name":"mdi:file-alert","tags":["files / folders","alert / error","file warning"]},{"name":"mdi:file-alert-outline","tags":["files / folders","alert / error","file warning outline"]},{"name":"mdi:file-arrow-left-right","tags":["files / folders","file exchange","file transfer","file swap"]},{"name":"mdi:file-arrow-left-right-outline","tags":["files / folders","file exchange outline","file swap outline","file transfer outline"]},{"name":"mdi:file-arrow-up-down","tags":["files / folders","file exchange","file swap","file transfer","file upload download"]},{"name":"mdi:file-arrow-up-down-outline","tags":["files / folders","file exchange outline","file swap outline","file transfer outline","file upload download outline"]},{"name":"mdi:file-cabinet","tags":["files / folders","filing cabinet"]},{"name":"mdi:file-cad","tags":["files / folders"]},{"name":"mdi:file-cad-box","tags":["files / folders"]},{"name":"mdi:file-cancel","tags":["files / folders","ban","forbid"]},{"name":"mdi:file-cancel-outline","tags":["files / folders","ban","forbid"]},{"name":"mdi:file-certificate","tags":["files / folders"]},{"name":"mdi:file-certificate-outline","tags":["files / folders"]},{"name":"mdi:file-chart","tags":["files / folders","file report","file graph"]},{"name":"mdi:file-chart-check","tags":["files / folders"]},{"name":"mdi:file-chart-check-outline","tags":["files / folders"]},{"name":"mdi:file-chart-outline","tags":["files / folders","file graph outline","file report outline"]},{"name":"mdi:file-check","tags":["files / folders","file tick"]},{"name":"mdi:file-check-outline","tags":["files / folders"]},{"name":"mdi:file-clock","tags":["files / folders","date / time"]},{"name":"mdi:file-clock-outline","tags":["files / folders","date / time"]},{"name":"mdi:file-cloud","tags":["cloud","files / folders"]},{"name":"mdi:file-cloud-outline","tags":["files / folders","cloud"]},{"name":"mdi:file-code","tags":["files / folders","developer / languages"]},{"name":"mdi:file-code-outline","tags":["files / folders","developer / languages"]},{"name":"mdi:file-cog","tags":["settings","files / folders","file settings cog"]},{"name":"mdi:file-cog-outline","tags":["settings","files / folders","file settings cog outline"]},{"name":"mdi:file-compare","tags":["files / folders"]},{"name":"mdi:file-delimited","tags":["files / folders","file csv"]},{"name":"mdi:file-delimited-outline","tags":["files / folders","file csv outline"]},{"name":"mdi:file-document","tags":["files / folders","file text"]},{"name":"mdi:file-document-alert","tags":["files / folders","alert / error","file document error","file text alert","file text error"]},{"name":"mdi:file-document-alert-outline","tags":["files / folders","alert / error","file document error outline","file text error outline","file text alert outline"]},{"name":"mdi:file-document-arrow-right","tags":["files / folders","file document move","file text move","file text arrow right"]},{"name":"mdi:file-document-arrow-right-outline","tags":["files / folders","file document move outline","file text move outline","file text arrow right outline"]},{"name":"mdi:file-document-check","tags":["files / folders","file document tick","file text tick","file text check"]},{"name":"mdi:file-document-check-outline","tags":["files / folders","file document tick outline","file text tick outline","file text check outline"]},{"name":"mdi:file-document-edit","tags":["edit / modify","files / folders","contract","file text edit"]},{"name":"mdi:file-document-edit-outline","tags":["edit / modify","files / folders","contract outline","file text edit outline"]},{"name":"mdi:file-document-minus","tags":["files / folders","file text minus"]},{"name":"mdi:file-document-minus-outline","tags":["files / folders","file text minus outline"]},{"name":"mdi:file-document-multiple","tags":["files / folders","file text multiple"]},{"name":"mdi:file-document-multiple-outline","tags":["files / folders","file text multiple outline"]},{"name":"mdi:file-document-plus","tags":["files / folders","file document add","file text add","file text plus"]},{"name":"mdi:file-document-plus-outline","tags":["files / folders","file document add outline","file text plus outline","file text add outline"]},{"name":"mdi:file-document-refresh","tags":["files / folders"]},{"name":"mdi:file-document-refresh-outline","tags":["files / folders"]},{"name":"mdi:file-document-remove","tags":["files / folders","file document delete","file text remove","file text delete"]},{"name":"mdi:file-document-remove-outline","tags":["files / folders","file document delete outline","file text remove outline","file text delete outline"]},{"name":"mdi:file-download","tags":["files / folders"]},{"name":"mdi:file-download-outline","tags":["files / folders"]},{"name":"mdi:file-edit","tags":["edit / modify","files / folders"]},{"name":"mdi:file-edit-outline","tags":["edit / modify","files / folders"]},{"name":"mdi:file-excel","tags":["files / folders"]},{"name":"mdi:file-excel-box-outline","tags":["files / folders"]},{"name":"mdi:file-excel-outline","tags":["files / folders"]},{"name":"mdi:file-export","tags":["files / folders"]},{"name":"mdi:file-export-outline","tags":["files / folders"]},{"name":"mdi:file-eye","tags":["files / folders"]},{"name":"mdi:file-eye-outline","tags":["files / folders"]},{"name":"mdi:file-gif-box","tags":["files / folders"]},{"name":"mdi:file-hidden","tags":["files / folders"]},{"name":"mdi:file-image","tags":["files / folders"]},{"name":"mdi:file-image-marker","tags":["files / folders","navigation","file image location"]},{"name":"mdi:file-image-marker-outline","tags":["files / folders","navigation","file image location outline"]},{"name":"mdi:file-image-minus","tags":["files / folders"]},{"name":"mdi:file-image-minus-outline","tags":["files / folders"]},{"name":"mdi:file-image-outline","tags":["files / folders"]},{"name":"mdi:file-image-plus","tags":["files / folders","file image add"]},{"name":"mdi:file-image-plus-outline","tags":["files / folders","file image add outline"]},{"name":"mdi:file-image-remove","tags":["files / folders"]},{"name":"mdi:file-image-remove-outline","tags":["files / folders"]},{"name":"mdi:file-import","tags":["files / folders"]},{"name":"mdi:file-import-outline","tags":["files / folders"]},{"name":"mdi:file-jpg-box","tags":["files / folders","file jpeg box","image jpg box","image jpeg box"]},{"name":"mdi:file-key","tags":["files / folders"]},{"name":"mdi:file-key-outline","tags":["files / folders"]},{"name":"mdi:file-link","tags":["files / folders"]},{"name":"mdi:file-link-outline","tags":["files / folders"]},{"name":"mdi:file-lock","tags":["lock","files / folders"]},{"name":"mdi:file-lock-open","tags":["lock","files / folders"]},{"name":"mdi:file-lock-open-outline","tags":["lock","files / folders"]},{"name":"mdi:file-lock-outline","tags":["files / folders","lock"]},{"name":"mdi:file-marker","tags":["files / folders","navigation","file location"]},{"name":"mdi:file-marker-outline","tags":["files / folders","navigation","file location outline"]},{"name":"mdi:file-minus","tags":["files / folders"]},{"name":"mdi:file-minus-outline","tags":["files / folders"]},{"name":"mdi:file-move","tags":["files / folders"]},{"name":"mdi:file-move-outline","tags":["files / folders"]},{"name":"mdi:file-multiple","tags":["files / folders","files"]},{"name":"mdi:file-multiple-outline","tags":["files / folders"]},{"name":"mdi:file-music","tags":["files / folders","music"]},{"name":"mdi:file-music-outline","tags":["files / folders","music"]},{"name":"mdi:file-pdf-box","tags":["files / folders","file acrobat box","adobe acrobat"]},{"name":"mdi:file-percent","tags":["files / folders"]},{"name":"mdi:file-percent-outline","tags":["files / folders"]},{"name":"mdi:file-phone","tags":["files / folders","cellphone / phone"]},{"name":"mdi:file-phone-outline","tags":["files / folders","cellphone / phone"]},{"name":"mdi:file-plus","tags":["files / folders","note add"]},{"name":"mdi:file-plus-outline","tags":["files / folders"]},{"name":"mdi:file-png-box","tags":["files / folders"]},{"name":"mdi:file-powerpoint","tags":["files / folders"]},{"name":"mdi:file-powerpoint-box-outline","tags":["files / folders"]},{"name":"mdi:file-powerpoint-outline","tags":["files / folders"]},{"name":"mdi:file-question","tags":["files / folders"]},{"name":"mdi:file-question-outline","tags":["files / folders"]},{"name":"mdi:file-refresh","tags":["files / folders"]},{"name":"mdi:file-refresh-outline","tags":["files / folders"]},{"name":"mdi:file-remove","tags":["files / folders"]},{"name":"mdi:file-remove-outline","tags":["files / folders"]},{"name":"mdi:file-replace","tags":["files / folders"]},{"name":"mdi:file-replace-outline","tags":["files / folders"]},{"name":"mdi:file-restore-outline","tags":["files / folders"]},{"name":"mdi:file-rotate-left","tags":["files / folders","file rotate counter clockwise","file rotate ccw"]},{"name":"mdi:file-rotate-left-outline","tags":["files / folders","file rotate counter clockwise outline","file rotate ccw outline"]},{"name":"mdi:file-rotate-right","tags":["files / folders","file rotate clockwise"]},{"name":"mdi:file-rotate-right-outline","tags":["files / folders","file rotate clockwise"]},{"name":"mdi:file-search","tags":["files / folders"]},{"name":"mdi:file-search-outline","tags":["files / folders"]},{"name":"mdi:file-send","tags":["files / folders","file move"]},{"name":"mdi:file-send-outline","tags":["files / folders"]},{"name":"mdi:file-settings","tags":["settings","files / folders"]},{"name":"mdi:file-settings-outline","tags":["settings","files / folders"]},{"name":"mdi:file-sign","tags":["banking","files / folders","contract sign","document sign"]},{"name":"mdi:file-star","tags":["files / folders","file favorite"]},{"name":"mdi:file-star-four-points","tags":["files / folders","file auto"]},{"name":"mdi:file-star-four-points-outline","tags":["files / folders","file auto outline"]},{"name":"mdi:file-star-outline","tags":["files / folders","file favorite outline"]},{"name":"mdi:file-swap","tags":["files / folders","file transfer"]},{"name":"mdi:file-swap-outline","tags":["files / folders","file transfer outline"]},{"name":"mdi:file-sync","tags":["files / folders"]},{"name":"mdi:file-sync-outline","tags":["files / folders"]},{"name":"mdi:file-table","tags":["files / folders"]},{"name":"mdi:file-table-box","tags":["files / folders"]},{"name":"mdi:file-table-box-multiple","tags":["files / folders"]},{"name":"mdi:file-table-box-multiple-outline","tags":["files / folders"]},{"name":"mdi:file-table-box-outline","tags":["files / folders"]},{"name":"mdi:file-table-outline","tags":["files / folders"]},{"name":"mdi:file-tree","tags":["files / folders","subtasks"]},{"name":"mdi:file-tree-outline","tags":["files / folders"]},{"name":"mdi:file-undo","tags":["files / folders","file revert","file discard"]},{"name":"mdi:file-undo-outline","tags":["files / folders"]},{"name":"mdi:file-upload","tags":["files / folders"]},{"name":"mdi:file-upload-outline","tags":["files / folders"]},{"name":"mdi:file-video","tags":["video / movie","files / folders"]},{"name":"mdi:file-video-outline","tags":["files / folders"]},{"name":"mdi:file-word","tags":["files / folders"]},{"name":"mdi:file-word-box-outline","tags":["files / folders"]},{"name":"mdi:file-word-outline","tags":["files / folders"]},{"name":"mdi:file-xml-box","tags":["files / folders"]},{"name":"mdi:filmstrip-box","tags":[]},{"name":"mdi:filmstrip-off","tags":["video / movie"]},{"name":"mdi:filter","tags":["funnel"]},{"name":"mdi:filter-check","tags":["funnel check"]},{"name":"mdi:filter-check-outline","tags":["funnel check outline"]},{"name":"mdi:filter-cog","tags":["settings","funnel settings","filter settings","funnel cog","filter gear","funnel gear"]},{"name":"mdi:filter-cog-outline","tags":["settings","filter settings outline","filter gear outline","funnel cog outline","funnel settings outline","funnel gear outline"]},{"name":"mdi:filter-menu","tags":[]},{"name":"mdi:filter-menu-outline","tags":[]},{"name":"mdi:filter-minus","tags":["funnel minus"]},{"name":"mdi:filter-minus-outline","tags":["funnel minus outline"]},{"name":"mdi:filter-multiple","tags":["funnel multiple"]},{"name":"mdi:filter-multiple-outline","tags":["funnel multiple outline"]},{"name":"mdi:filter-off","tags":[]},{"name":"mdi:filter-off-outline","tags":[]},{"name":"mdi:filter-outline","tags":["funnel outline"]},{"name":"mdi:filter-plus","tags":["funnel plus"]},{"name":"mdi:filter-plus-outline","tags":["funnel plus outline"]},{"name":"mdi:filter-remove","tags":["funnel remove"]},{"name":"mdi:filter-remove-outline","tags":["funnel remove outline"]},{"name":"mdi:filter-settings","tags":["settings","funnel settings"]},{"name":"mdi:filter-settings-outline","tags":["settings","funnel settings outline"]},{"name":"mdi:filter-variant-minus","tags":[]},{"name":"mdi:filter-variant-plus","tags":[]},{"name":"mdi:filter-variant-remove","tags":[]},{"name":"mdi:fingerprint-off","tags":[]},{"name":"mdi:fire-alert","tags":["alert / error","home automation","flame alert"]},{"name":"mdi:fire-circle","tags":["home automation","flame circle","hot circle","gas circle","natural gas circle"]},{"name":"mdi:fire-extinguisher","tags":["hardware / tools","home automation"]},{"name":"mdi:fire-hydrant","tags":[]},{"name":"mdi:fire-hydrant-alert","tags":["alert / error"]},{"name":"mdi:fire-hydrant-off","tags":[]},{"name":"mdi:fire-off","tags":["home automation","flame off"]},{"name":"mdi:fire-truck","tags":["transportation + road","fire engine"]},{"name":"mdi:fireplace","tags":["home automation"]},{"name":"mdi:fireplace-off","tags":["home automation"]},{"name":"mdi:firewire","tags":[]},{"name":"mdi:firework","tags":["holiday","bottle rocket"]},{"name":"mdi:firework-off","tags":[]},{"name":"mdi:fish","tags":["animal","food / drink"]},{"name":"mdi:fish-off","tags":["food / drink"]},{"name":"mdi:fishbowl","tags":["animal","aquarium"]},{"name":"mdi:fishbowl-outline","tags":["animal","aquarium outline"]},{"name":"mdi:fit-to-page","tags":["text / content / format","arrow"]},{"name":"mdi:fit-to-page-outline","tags":["text / content / format","arrow"]},{"name":"mdi:flag-checkered","tags":["sport","goal"]},{"name":"mdi:flag-minus","tags":[]},{"name":"mdi:flag-minus-outline","tags":[]},{"name":"mdi:flag-off","tags":[]},{"name":"mdi:flag-off-outline","tags":[]},{"name":"mdi:flag-plus","tags":["flag add"]},{"name":"mdi:flag-plus-outline","tags":[]},{"name":"mdi:flag-remove","tags":[]},{"name":"mdi:flag-remove-outline","tags":[]},{"name":"mdi:flag-triangle","tags":["milestone"]},{"name":"mdi:flag-variant","tags":[]},{"name":"mdi:flag-variant-minus","tags":[]},{"name":"mdi:flag-variant-minus-outline","tags":[]},{"name":"mdi:flag-variant-off","tags":[]},{"name":"mdi:flag-variant-off-outline","tags":[]},{"name":"mdi:flag-variant-outline","tags":[]},{"name":"mdi:flag-variant-plus","tags":[]},{"name":"mdi:flag-variant-plus-outline","tags":[]},{"name":"mdi:flag-variant-remove","tags":[]},{"name":"mdi:flag-variant-remove-outline","tags":[]},{"name":"mdi:flash-alert","tags":["weather","alert / error","lightning alert","storm advisory"]},{"name":"mdi:flash-alert-outline","tags":["weather","alert / error","lightning alert outline","storm advisory outline"]},{"name":"mdi:flash-off-outline","tags":[]},{"name":"mdi:flash-outline","tags":["weather","lightning bolt outline"]},{"name":"mdi:flash-red-eye","tags":[]},{"name":"mdi:flash-triangle","tags":["home automation","high voltage"]},{"name":"mdi:flash-triangle-outline","tags":["home automation","high voltage outline"]},{"name":"mdi:flashlight","tags":["torch"]},{"name":"mdi:flashlight-off","tags":["torch off"]},{"name":"mdi:flask","tags":["science","gaming / rpg"]},{"name":"mdi:flask-empty","tags":["science","gaming / rpg"]},{"name":"mdi:flask-empty-minus","tags":["science"]},{"name":"mdi:flask-empty-minus-outline","tags":["science"]},{"name":"mdi:flask-empty-off","tags":[]},{"name":"mdi:flask-empty-off-outline","tags":[]},{"name":"mdi:flask-empty-outline","tags":["science","gaming / rpg"]},{"name":"mdi:flask-empty-plus","tags":["science"]},{"name":"mdi:flask-empty-plus-outline","tags":["science"]},{"name":"mdi:flask-empty-remove","tags":["science"]},{"name":"mdi:flask-empty-remove-outline","tags":["science"]},{"name":"mdi:flask-minus","tags":["science"]},{"name":"mdi:flask-minus-outline","tags":["science"]},{"name":"mdi:flask-off","tags":[]},{"name":"mdi:flask-off-outline","tags":[]},{"name":"mdi:flask-outline","tags":["science","gaming / rpg"]},{"name":"mdi:flask-plus","tags":["science"]},{"name":"mdi:flask-plus-outline","tags":["science"]},{"name":"mdi:flask-remove","tags":["science"]},{"name":"mdi:flask-remove-outline","tags":["science"]},{"name":"mdi:flask-round-bottom","tags":["science"]},{"name":"mdi:flask-round-bottom-empty","tags":["science"]},{"name":"mdi:flask-round-bottom-empty-outline","tags":["science"]},{"name":"mdi:flask-round-bottom-outline","tags":["science"]},{"name":"mdi:fleur-de-lis","tags":[]},{"name":"mdi:floor-lamp","tags":["home automation","floor light"]},{"name":"mdi:floor-lamp-dual","tags":["home automation","floor light dual"]},{"name":"mdi:floor-lamp-dual-outline","tags":["home automation","floor light dual outline"]},{"name":"mdi:floor-lamp-outline","tags":["home automation","floor light outline"]},{"name":"mdi:floor-lamp-torchiere","tags":["home automation","floor light torchiere"]},{"name":"mdi:floor-lamp-torchiere-outline","tags":["home automation"]},{"name":"mdi:floor-lamp-torchiere-variant","tags":["home automation","floor light torchiere variant"]},{"name":"mdi:floor-lamp-torchiere-variant-outline","tags":["home automation","floor light torchiere variant outline"]},{"name":"mdi:floor-plan","tags":["home automation"]},{"name":"mdi:floppy","tags":[]},{"name":"mdi:floppy-variant","tags":[]},{"name":"mdi:flower-pollen","tags":["nature","agriculture","allergy"]},{"name":"mdi:flower-pollen-outline","tags":["nature","agriculture","allergy outline"]},{"name":"mdi:flower-poppy","tags":["nature","agriculture","plant"]},{"name":"mdi:flower-tulip","tags":["nature","agriculture","plant"]},{"name":"mdi:flower-tulip-outline","tags":["nature","agriculture","plant"]},{"name":"mdi:focus-auto","tags":["photography"]},{"name":"mdi:focus-field","tags":["photography"]},{"name":"mdi:focus-field-horizontal","tags":["photography"]},{"name":"mdi:focus-field-vertical","tags":["photography"]},{"name":"mdi:folder-alert","tags":["files / folders","alert / error","folder warning"]},{"name":"mdi:folder-alert-outline","tags":["files / folders","alert / error","folder warning outline"]},{"name":"mdi:folder-arrow-down","tags":["files / folders","folder download"]},{"name":"mdi:folder-arrow-down-outline","tags":["files / folders","folder download outline"]},{"name":"mdi:folder-arrow-left","tags":["files / folders"]},{"name":"mdi:folder-arrow-left-outline","tags":["files / folders"]},{"name":"mdi:folder-arrow-left-right","tags":["files / folders"]},{"name":"mdi:folder-arrow-left-right-outline","tags":["files / folders"]},{"name":"mdi:folder-arrow-right","tags":["files / folders"]},{"name":"mdi:folder-arrow-right-outline","tags":["files / folders"]},{"name":"mdi:folder-arrow-up","tags":["files / folders","folder upload"]},{"name":"mdi:folder-arrow-up-down","tags":["files / folders","folder transfer"]},{"name":"mdi:folder-arrow-up-down-outline","tags":["files / folders","folder transfer outline"]},{"name":"mdi:folder-arrow-up-outline","tags":["files / folders","folder upload outline"]},{"name":"mdi:folder-cancel","tags":["files / folders"]},{"name":"mdi:folder-cancel-outline","tags":["files / folders"]},{"name":"mdi:folder-check","tags":["files / folders"]},{"name":"mdi:folder-check-outline","tags":["files / folders"]},{"name":"mdi:folder-clock","tags":["files / folders","date / time"]},{"name":"mdi:folder-clock-outline","tags":["files / folders","date / time"]},{"name":"mdi:folder-cog","tags":["settings","files / folders","folder cog"]},{"name":"mdi:folder-cog-outline","tags":["settings","files / folders","folder cog outline"]},{"name":"mdi:folder-download","tags":["files / folders"]},{"name":"mdi:folder-download-outline","tags":["files / folders"]},{"name":"mdi:folder-edit","tags":["files / folders","edit / modify"]},{"name":"mdi:folder-edit-outline","tags":["edit / modify","files / folders"]},{"name":"mdi:folder-eye","tags":["files / folders"]},{"name":"mdi:folder-eye-outline","tags":["files / folders"]},{"name":"mdi:folder-file","tags":["files / folders"]},{"name":"mdi:folder-file-outline","tags":["files / folders"]},{"name":"mdi:folder-heart","tags":["files / folders"]},{"name":"mdi:folder-heart-outline","tags":["files / folders"]},{"name":"mdi:folder-hidden","tags":["files / folders"]},{"name":"mdi:folder-home","tags":["files / folders","home automation","folder house"]},{"name":"mdi:folder-home-outline","tags":["files / folders","home automation","folder house outline"]},{"name":"mdi:folder-image","tags":["files / folders"]},{"name":"mdi:folder-information","tags":["files / folders"]},{"name":"mdi:folder-information-outline","tags":["files / folders"]},{"name":"mdi:folder-key","tags":["files / folders"]},{"name":"mdi:folder-key-network","tags":["files / folders"]},{"name":"mdi:folder-key-network-outline","tags":["files / folders"]},{"name":"mdi:folder-key-outline","tags":["files / folders"]},{"name":"mdi:folder-lock","tags":["lock","files / folders"]},{"name":"mdi:folder-lock-open","tags":["lock","files / folders"]},{"name":"mdi:folder-lock-open-outline","tags":["files / folders","lock"]},{"name":"mdi:folder-lock-outline","tags":["files / folders","lock"]},{"name":"mdi:folder-marker","tags":["geographic information system","files / folders","navigation","folder location"]},{"name":"mdi:folder-marker-outline","tags":["geographic information system","files / folders","navigation","folder location outline"]},{"name":"mdi:folder-minus","tags":["files / folders"]},{"name":"mdi:folder-minus-outline","tags":["files / folders"]},{"name":"mdi:folder-move-outline","tags":["files / folders"]},{"name":"mdi:folder-multiple","tags":["files / folders","folders"]},{"name":"mdi:folder-multiple-outline","tags":["files / folders","folders outline"]},{"name":"mdi:folder-multiple-plus","tags":["files / folders"]},{"name":"mdi:folder-multiple-plus-outline","tags":["files / folders"]},{"name":"mdi:folder-music","tags":["files / folders","music"]},{"name":"mdi:folder-music-outline","tags":["files / folders","music"]},{"name":"mdi:folder-network","tags":["files / folders"]},{"name":"mdi:folder-network-outline","tags":["files / folders"]},{"name":"mdi:folder-off","tags":["files / folders"]},{"name":"mdi:folder-off-outline","tags":["files / folders"]},{"name":"mdi:folder-open","tags":["files / folders"]},{"name":"mdi:folder-open-outline","tags":["files / folders"]},{"name":"mdi:folder-play","tags":["files / folders","folder media","folder music","folder video"]},{"name":"mdi:folder-play-outline","tags":["files / folders","folder media outline","folder music outline","folder video outline"]},{"name":"mdi:folder-plus","tags":["files / folders","create new folder","folder add"]},{"name":"mdi:folder-pound","tags":["files / folders","developer / languages","folder hash"]},{"name":"mdi:folder-pound-outline","tags":["files / folders","developer / languages","folder hash outline"]},{"name":"mdi:folder-question","tags":["files / folders","folder help"]},{"name":"mdi:folder-question-outline","tags":["files / folders","folder help outline"]},{"name":"mdi:folder-refresh","tags":["files / folders"]},{"name":"mdi:folder-refresh-outline","tags":["files / folders"]},{"name":"mdi:folder-remove","tags":["files / folders"]},{"name":"mdi:folder-remove-outline","tags":["files / folders"]},{"name":"mdi:folder-search","tags":["files / folders"]},{"name":"mdi:folder-search-outline","tags":["files / folders"]},{"name":"mdi:folder-settings","tags":["settings","files / folders"]},{"name":"mdi:folder-settings-outline","tags":["settings","files / folders"]},{"name":"mdi:folder-star-multiple","tags":["files / folders","folder favorite multiple"]},{"name":"mdi:folder-star-multiple-outline","tags":["files / folders","folder favorite multiple outline"]},{"name":"mdi:folder-swap","tags":["files / folders","folder transfer"]},{"name":"mdi:folder-swap-outline","tags":["files / folders","folder transfer outline"]},{"name":"mdi:folder-sync","tags":["files / folders"]},{"name":"mdi:folder-sync-outline","tags":["files / folders"]},{"name":"mdi:folder-table","tags":["files / folders"]},{"name":"mdi:folder-table-outline","tags":["files / folders"]},{"name":"mdi:folder-text","tags":["files / folders"]},{"name":"mdi:folder-text-outline","tags":["files / folders"]},{"name":"mdi:folder-upload","tags":["files / folders"]},{"name":"mdi:folder-upload-outline","tags":["files / folders"]},{"name":"mdi:folder-wrench","tags":["files / folders","folder settings"]},{"name":"mdi:folder-wrench-outline","tags":["files / folders","folder settings outline"]},{"name":"mdi:folder-zip","tags":["files / folders","compressed folder"]},{"name":"mdi:folder-zip-outline","tags":["files / folders","compressed folder outline"]},{"name":"mdi:food-apple","tags":["food / drink","agriculture"]},{"name":"mdi:food-apple-outline","tags":["food / drink","agriculture"]},{"name":"mdi:food-croissant","tags":["food / drink"]},{"name":"mdi:food-drumstick","tags":["food / drink","chicken leg","turkey leg","meat"]},{"name":"mdi:food-drumstick-off","tags":["food / drink","chicken leg off","turkey leg off","meat off"]},{"name":"mdi:food-drumstick-off-outline","tags":["food / drink","chicken leg off outline","turkey leg off outline","meat off outline"]},{"name":"mdi:food-drumstick-outline","tags":["food / drink","chicken leg outline","turkey leg outline","meat outline"]},{"name":"mdi:food-halal","tags":["food / drink","food muslim","dietary restriction"]},{"name":"mdi:food-hot-dog","tags":["food / drink","food weiner","food frankfurter"]},{"name":"mdi:food-kosher","tags":["food / drink","food jewish","dietary restriction"]},{"name":"mdi:food-steak","tags":["food / drink","meat","beef"]},{"name":"mdi:food-steak-off","tags":["food / drink","meat off","beef off"]},{"name":"mdi:food-turkey","tags":["food / drink","holiday","dinner","thanksgiving"]},{"name":"mdi:food-variant","tags":["food / drink"]},{"name":"mdi:food-variant-off","tags":["food / drink"]},{"name":"mdi:foot-print","tags":[]},{"name":"mdi:football-australian","tags":["sport"]},{"name":"mdi:football-helmet","tags":["sport"]},{"name":"mdi:forest-outline","tags":["nature","agriculture","places","forestry outline","pine tree multiple outline"]},{"name":"mdi:forklift","tags":["transportation + road"]},{"name":"mdi:form-dropdown","tags":["form"]},{"name":"mdi:form-select","tags":["form"]},{"name":"mdi:form-textarea","tags":["form"]},{"name":"mdi:form-textbox","tags":["form","rename"]},{"name":"mdi:form-textbox-lock","tags":["form","lock"]},{"name":"mdi:form-textbox-password","tags":["form"]},{"name":"mdi:format-align-bottom","tags":["text / content / format"]},{"name":"mdi:format-align-middle","tags":["text / content / format"]},{"name":"mdi:format-align-top","tags":["text / content / format"]},{"name":"mdi:format-annotation-minus","tags":["text / content / format"]},{"name":"mdi:format-annotation-plus","tags":["text / content / format","format annotation add"]},{"name":"mdi:format-color-highlight","tags":["color","text / content / format","format colour highlight"]},{"name":"mdi:format-color-marker-cancel","tags":["text / content / format","color","format color redact"]},{"name":"mdi:format-columns","tags":["text / content / format"]},{"name":"mdi:format-float-center","tags":["text / content / format","format float centre"]},{"name":"mdi:format-float-left","tags":["text / content / format"]},{"name":"mdi:format-float-none","tags":["text / content / format"]},{"name":"mdi:format-float-right","tags":["text / content / format"]},{"name":"mdi:format-font","tags":["text / content / format"]},{"name":"mdi:format-font-size-decrease","tags":["text / content / format"]},{"name":"mdi:format-font-size-increase","tags":["text / content / format"]},{"name":"mdi:format-header-1","tags":["text / content / format","format heading 1"]},{"name":"mdi:format-header-2","tags":["text / content / format","format heading 2"]},{"name":"mdi:format-header-3","tags":["text / content / format","format heading 3"]},{"name":"mdi:format-header-4","tags":["text / content / format","format heading 4"]},{"name":"mdi:format-header-5","tags":["text / content / format","format heading 5"]},{"name":"mdi:format-header-6","tags":["text / content / format","format heading 6"]},{"name":"mdi:format-header-decrease","tags":["text / content / format","format heading decease"]},{"name":"mdi:format-header-equal","tags":["text / content / format","format heading equal"]},{"name":"mdi:format-header-increase","tags":["text / content / format","format heading increase"]},{"name":"mdi:format-header-pound","tags":["text / content / format","format header hash","format heading pound","format heading hash","format heading markdown"]},{"name":"mdi:format-horizontal-align-center","tags":["text / content / format","format horizontal align centre","arrow horizontal collapse"]},{"name":"mdi:format-horizontal-align-left","tags":["text / content / format"]},{"name":"mdi:format-horizontal-align-right","tags":["text / content / format"]},{"name":"mdi:format-letter-case","tags":["text / content / format"]},{"name":"mdi:format-letter-case-lower","tags":["text / content / format","format lowercase"]},{"name":"mdi:format-letter-case-upper","tags":["text / content / format","format uppercase"]},{"name":"mdi:format-letter-ends-with","tags":["text / content / format"]},{"name":"mdi:format-letter-matches","tags":["text / content / format"]},{"name":"mdi:format-letter-spacing","tags":["text / content / format","format kerning"]},{"name":"mdi:format-letter-spacing-variant","tags":["text / content / format"]},{"name":"mdi:format-letter-starts-with","tags":["text / content / format"]},{"name":"mdi:format-line-height","tags":["text / content / format"]},{"name":"mdi:format-list-bulleted-triangle","tags":["text / content / format"]},{"name":"mdi:format-list-bulleted-type","tags":["text / content / format"]},{"name":"mdi:format-list-checks","tags":["text / content / format","to do"]},{"name":"mdi:format-list-group","tags":["text / content / format"]},{"name":"mdi:format-list-group-plus","tags":["text / content / format","format list group add"]},{"name":"mdi:format-list-text","tags":["text / content / format"]},{"name":"mdi:format-overline","tags":["text / content / format"]},{"name":"mdi:format-page-split","tags":["text / content / format"]},{"name":"mdi:format-paragraph","tags":["text / content / format"]},{"name":"mdi:format-paragraph-spacing","tags":["text / content / format"]},{"name":"mdi:format-pilcrow","tags":["text / content / format"]},{"name":"mdi:format-quote-close-outline","tags":["text / content / format"]},{"name":"mdi:format-quote-open","tags":["text / content / format"]},{"name":"mdi:format-quote-open-outline","tags":["text / content / format"]},{"name":"mdi:format-section","tags":["text / content / format"]},{"name":"mdi:format-subscript","tags":["text / content / format"]},{"name":"mdi:format-superscript","tags":["text / content / format","math","exponent"]},{"name":"mdi:format-text","tags":["text / content / format"]},{"name":"mdi:format-text-rotation-down-vertical","tags":["text / content / format"]},{"name":"mdi:format-text-variant","tags":["text / content / format"]},{"name":"mdi:format-text-variant-outline","tags":["text / content / format"]},{"name":"mdi:format-underline-wavy","tags":["text / content / format"]},{"name":"mdi:format-wrap-inline","tags":["text / content / format"]},{"name":"mdi:format-wrap-square","tags":["text / content / format"]},{"name":"mdi:format-wrap-tight","tags":["text / content / format"]},{"name":"mdi:format-wrap-top-bottom","tags":["text / content / format"]},{"name":"mdi:forum-minus","tags":["chat minus","forum subtract","chat subtract"]},{"name":"mdi:forum-minus-outline","tags":["chat minus outline","forum subtract outline","chat subtract outline"]},{"name":"mdi:forum-plus","tags":["chat plus","forum add","chat add"]},{"name":"mdi:forum-plus-outline","tags":["chat plus outline","chat add outline","forum add outline"]},{"name":"mdi:forum-remove","tags":["forum delete","chat remove","chat delete"]},{"name":"mdi:forum-remove-outline","tags":["forum delete outline","chat remove outline","chat delete outline"]},{"name":"mdi:forwardburger","tags":[]},{"name":"mdi:fountain","tags":[]},{"name":"mdi:fountain-pen","tags":["drawing / art"]},{"name":"mdi:fountain-pen-tip","tags":["drawing / art"]},{"name":"mdi:fraction-one-half","tags":[]},{"name":"mdi:french-fries","tags":["food / drink","chips","finger chips","french fry","fried potatoes","fries","frites"]},{"name":"mdi:frequently-asked-questions","tags":["faq"]},{"name":"mdi:fridge","tags":["home automation","fridge filled","refrigerator","kitchen"]},{"name":"mdi:fridge-alert","tags":["home automation","alert / error"]},{"name":"mdi:fridge-alert-outline","tags":["home automation","alert / error"]},{"name":"mdi:fridge-bottom","tags":["home automation","fridge filled top","refrigerator bottom"]},{"name":"mdi:fridge-industrial","tags":["home automation"]},{"name":"mdi:fridge-industrial-alert","tags":["home automation","alert / error"]},{"name":"mdi:fridge-industrial-alert-outline","tags":["home automation","alert / error"]},{"name":"mdi:fridge-industrial-off","tags":["home automation"]},{"name":"mdi:fridge-industrial-off-outline","tags":["home automation"]},{"name":"mdi:fridge-industrial-outline","tags":["home automation"]},{"name":"mdi:fridge-off","tags":["home automation"]},{"name":"mdi:fridge-off-outline","tags":["home automation"]},{"name":"mdi:fridge-outline","tags":["home automation","kitchen","refrigerator outline"]},{"name":"mdi:fridge-top","tags":["home automation","fridge filled bottom","refrigerator top"]},{"name":"mdi:fridge-variant","tags":["home automation"]},{"name":"mdi:fridge-variant-alert","tags":["home automation","alert / error"]},{"name":"mdi:fridge-variant-alert-outline","tags":["home automation","alert / error"]},{"name":"mdi:fridge-variant-off","tags":["home automation"]},{"name":"mdi:fridge-variant-off-outline","tags":["home automation"]},{"name":"mdi:fridge-variant-outline","tags":["home automation"]},{"name":"mdi:fruit-cherries","tags":["food / drink","agriculture"]},{"name":"mdi:fruit-cherries-off","tags":["food / drink","agriculture"]},{"name":"mdi:fruit-citrus","tags":["food / drink","agriculture","fruit lemon","fruit lime"]},{"name":"mdi:fruit-citrus-off","tags":["food / drink","agriculture"]},{"name":"mdi:fruit-grapes","tags":["food / drink","agriculture"]},{"name":"mdi:fruit-grapes-outline","tags":["food / drink","agriculture"]},{"name":"mdi:fruit-pear","tags":["food / drink"]},{"name":"mdi:fruit-pineapple","tags":["food / drink","agriculture","fruit ananas"]},{"name":"mdi:fruit-watermelon","tags":["food / drink","agriculture"]},{"name":"mdi:fuel","tags":["automotive","petrol","gasoline"]},{"name":"mdi:fuel-cell","tags":["automotive","battery","battery"]},{"name":"mdi:function","tags":["math"]},{"name":"mdi:function-variant","tags":["math"]},{"name":"mdi:furigana-horizontal","tags":["text / content / format","ruby horizontal"]},{"name":"mdi:furigana-vertical","tags":["text / content / format","zhuyin","ruby vertical"]},{"name":"mdi:fuse","tags":["automotive"]},{"name":"mdi:fuse-alert","tags":["automotive","alert / error"]},{"name":"mdi:fuse-blade","tags":["automotive"]},{"name":"mdi:fuse-off","tags":["automotive"]},{"name":"mdi:gamepad-circle","tags":["gaming / rpg","controller circle"]},{"name":"mdi:gamepad-circle-down","tags":["gaming / rpg","controller circle down"]},{"name":"mdi:gamepad-circle-left","tags":["gaming / rpg","controller circle left"]},{"name":"mdi:gamepad-circle-outline","tags":["gaming / rpg","controller circle outline"]},{"name":"mdi:gamepad-circle-right","tags":["gaming / rpg","controller circle right"]},{"name":"mdi:gamepad-circle-up","tags":["gaming / rpg","controller circle up"]},{"name":"mdi:gamepad-down","tags":["gaming / rpg","controller down"]},{"name":"mdi:gamepad-left","tags":["gaming / rpg","controller left"]},{"name":"mdi:gamepad-outline","tags":["gaming / rpg","home automation","controller outline","games outline"]},{"name":"mdi:gamepad-right","tags":["gaming / rpg","controller right"]},{"name":"mdi:gamepad-round","tags":["gaming / rpg","controller round"]},{"name":"mdi:gamepad-round-down","tags":["gaming / rpg","controller round down"]},{"name":"mdi:gamepad-round-left","tags":["gaming / rpg","controller round left"]},{"name":"mdi:gamepad-round-outline","tags":["gaming / rpg","controller round outline"]},{"name":"mdi:gamepad-round-right","tags":["gaming / rpg","controller round right"]},{"name":"mdi:gamepad-round-up","tags":["gaming / rpg","controller round up"]},{"name":"mdi:gamepad-up","tags":["gaming / rpg","controller up"]},{"name":"mdi:gamepad-variant","tags":["gaming / rpg","controller variant"]},{"name":"mdi:gamepad-variant-outline","tags":["gaming / rpg","controller variant outline"]},{"name":"mdi:gamma","tags":["alpha / numeric"]},{"name":"mdi:gantry-crane","tags":[]},{"name":"mdi:garage","tags":["home automation"]},{"name":"mdi:garage-alert","tags":["home automation","alert / error","garage warning"]},{"name":"mdi:garage-alert-variant","tags":["home automation","alert / error"]},{"name":"mdi:garage-lock","tags":["home automation","lock"]},{"name":"mdi:garage-open","tags":["home automation"]},{"name":"mdi:garage-open-variant","tags":["home automation"]},{"name":"mdi:garage-variant","tags":["home automation"]},{"name":"mdi:garage-variant-lock","tags":["home automation","lock"]},{"name":"mdi:gas-burner","tags":["home automation","stove burner","cooktop burner","grill"]},{"name":"mdi:gas-cylinder","tags":["tank","oxygen tank"]},{"name":"mdi:gas-station-off","tags":[]},{"name":"mdi:gas-station-off-outline","tags":[]},{"name":"mdi:gate","tags":["home automation"]},{"name":"mdi:gate-alert","tags":["home automation","alert / error"]},{"name":"mdi:gate-and","tags":["logic gate and"]},{"name":"mdi:gate-arrow-left","tags":["home automation"]},{"name":"mdi:gate-arrow-right","tags":["home automation"]},{"name":"mdi:gate-buffer","tags":[]},{"name":"mdi:gate-nand","tags":["logic gate nand"]},{"name":"mdi:gate-nor","tags":["logic gate nor"]},{"name":"mdi:gate-not","tags":["logic gate not"]},{"name":"mdi:gate-open","tags":["home automation"]},{"name":"mdi:gate-or","tags":["logic gate or"]},{"name":"mdi:gate-xnor","tags":["logic gate xnor"]},{"name":"mdi:gate-xor","tags":["logic gate xor"]},{"name":"mdi:gauge-empty","tags":["automotive","home automation"]},{"name":"mdi:gauge-full","tags":["automotive","home automation"]},{"name":"mdi:gauge-low","tags":["automotive","home automation"]},{"name":"mdi:gavel","tags":["court hammer"]},{"name":"mdi:gender-female","tags":["venus"]},{"name":"mdi:gender-male","tags":["mars"]},{"name":"mdi:gender-male-female","tags":[]},{"name":"mdi:gender-male-female-variant","tags":["mercury"]},{"name":"mdi:gender-non-binary","tags":["gender enby"]},{"name":"mdi:gender-transgender","tags":[]},{"name":"mdi:gesture-double-tap","tags":["interaction double tap","hand double tap"]},{"name":"mdi:gesture-pinch","tags":[]},{"name":"mdi:gesture-spread","tags":[]},{"name":"mdi:gesture-swipe-down","tags":[]},{"name":"mdi:gesture-swipe-horizontal","tags":[]},{"name":"mdi:gesture-swipe-left","tags":[]},{"name":"mdi:gesture-swipe-right","tags":[]},{"name":"mdi:gesture-swipe-up","tags":[]},{"name":"mdi:gesture-swipe-vertical","tags":[]},{"name":"mdi:gesture-tap","tags":["interaction tap","hand tap","gesture touch"]},{"name":"mdi:gesture-tap-box","tags":["gesture touch box"]},{"name":"mdi:gesture-tap-button","tags":["form","call to action","cta","button pointer","gesture touch button"]},{"name":"mdi:gesture-two-double-tap","tags":[]},{"name":"mdi:gesture-two-tap","tags":[]},{"name":"mdi:ghost","tags":["gaming / rpg","inky","blinky","pinky","clyde"]},{"name":"mdi:ghost-off","tags":["gaming / rpg"]},{"name":"mdi:ghost-off-outline","tags":["gaming / rpg"]},{"name":"mdi:ghost-outline","tags":["gaming / rpg"]},{"name":"mdi:gift","tags":["holiday","present","package","donate"]},{"name":"mdi:gift-off","tags":["holiday","present off","package off","donate off"]},{"name":"mdi:gift-off-outline","tags":["holiday","present off outline","package off outline","donate off outline"]},{"name":"mdi:gift-open","tags":["holiday","present open","package open"]},{"name":"mdi:gift-open-outline","tags":["holiday","present open outline","package open outline"]},{"name":"mdi:gift-outline","tags":["shopping","holiday","donate outline","present outline","package outline"]},{"name":"mdi:glass-cocktail-off","tags":["food / drink"]},{"name":"mdi:glass-flute","tags":["food / drink","alcohol","cocktail","cup","drink"]},{"name":"mdi:glass-fragile","tags":["food / drink","glass broken"]},{"name":"mdi:glass-mug","tags":["food / drink","pub","bar","beer","alcohol","cup","drink","local bar"]},{"name":"mdi:glass-mug-off","tags":["food / drink"]},{"name":"mdi:glass-mug-variant","tags":["food / drink","pub","bar","beer","drink","alcohol","cup","local bar"]},{"name":"mdi:glass-mug-variant-off","tags":["food / drink"]},{"name":"mdi:glass-pint-outline","tags":["food / drink"]},{"name":"mdi:glass-stange","tags":["food / drink","alcohol","bar","cocktail","cup","drink"]},{"name":"mdi:glass-tulip","tags":["food / drink","bar","alcohol","cocktail","cup","drink"]},{"name":"mdi:glass-wine","tags":["food / drink","bar","alcohol","cocktail","cup","drink"]},{"name":"mdi:glasses","tags":["clothing"]},{"name":"mdi:globe-light","tags":["home automation"]},{"name":"mdi:globe-light-outline","tags":["home automation"]},{"name":"mdi:globe-model","tags":[]},{"name":"mdi:go-kart","tags":["sport","cart"]},{"name":"mdi:go-kart-track","tags":[]},{"name":"mdi:gold","tags":[]},{"name":"mdi:golf-cart","tags":["sport","transportation + other"]},{"name":"mdi:gondola","tags":["transportation + other","cable car"]},{"name":"mdi:gradient-horizontal","tags":["drawing / art"]},{"name":"mdi:graph","tags":["dependency","dependencies"]},{"name":"mdi:graph-outline","tags":["dependency","dependencies"]},{"name":"mdi:grave-stone","tags":["holiday","headstone","tombstone","cemetery","graveyard"]},{"name":"mdi:greater-than","tags":["math"]},{"name":"mdi:greater-than-or-equal","tags":["math"]},{"name":"mdi:greenhouse","tags":["home automation","agriculture","nature","glasshouse","hothouse","shed"]},{"name":"mdi:grid-large","tags":[]},{"name":"mdi:group","tags":[]},{"name":"mdi:guitar-acoustic","tags":["music"]},{"name":"mdi:guitar-electric","tags":["music"]},{"name":"mdi:guitar-pick","tags":["music"]},{"name":"mdi:guitar-pick-outline","tags":["music"]},{"name":"mdi:guy-fawkes-mask","tags":[]},{"name":"mdi:hair-dryer","tags":["health / beauty"]},{"name":"mdi:hair-dryer-outline","tags":["health / beauty"]},{"name":"mdi:halloween","tags":["holiday","pumpkin face","pumpkin carved","jack o lantern","emoji halloween","emoticon halloween"]},{"name":"mdi:hamburger","tags":["food / drink","burger","fast food","food"]},{"name":"mdi:hamburger-check","tags":["food / drink","burger check"]},{"name":"mdi:hamburger-minus","tags":["food / drink","burger minus"]},{"name":"mdi:hamburger-off","tags":["food / drink","burger off","fast food off","food off"]},{"name":"mdi:hamburger-plus","tags":["food / drink","burger plus","burger add"]},{"name":"mdi:hamburger-remove","tags":["food / drink","burger remove"]},{"name":"mdi:hammer-sickle","tags":["communism"]},{"name":"mdi:hand-back-left-off","tags":[]},{"name":"mdi:hand-back-left-off-outline","tags":[]},{"name":"mdi:hand-back-right-off","tags":[]},{"name":"mdi:hand-back-right-off-outline","tags":[]},{"name":"mdi:hand-clap","tags":["applause"]},{"name":"mdi:hand-clap-off","tags":["applause off"]},{"name":"mdi:hand-coin","tags":["banking","charity","donation"]},{"name":"mdi:hand-coin-outline","tags":["banking","charity outline","donation outline"]},{"name":"mdi:hand-cycle","tags":["sport","hand bike"]},{"name":"mdi:hand-extended","tags":["hand open","hand palm"]},{"name":"mdi:hand-extended-outline","tags":["hand open outline","hand palm outline"]},{"name":"mdi:hand-heart-outline","tags":[]},{"name":"mdi:hand-okay","tags":[]},{"name":"mdi:hand-peace","tags":[]},{"name":"mdi:hand-peace-variant","tags":[]},{"name":"mdi:hand-pointing-down","tags":[]},{"name":"mdi:hand-pointing-left","tags":[]},{"name":"mdi:hand-pointing-right","tags":[]},{"name":"mdi:hand-pointing-up","tags":[]},{"name":"mdi:hand-saw","tags":["hardware / tools"]},{"name":"mdi:hand-water","tags":["medical / hospital","hand wash"]},{"name":"mdi:handcuffs","tags":[]},{"name":"mdi:hands-pray","tags":[]},{"name":"mdi:handshake","tags":["business","deal","help","partnership"]},{"name":"mdi:handshake-outline","tags":["business outline","deal outline","help outline","partnership outline"]},{"name":"mdi:hanger","tags":["clothing","home automation","coat hanger","clothes hanger","closet"]},{"name":"mdi:hard-hat","tags":["hardware / tools","clothing","helmet"]},{"name":"mdi:harddisk","tags":["hdd"]},{"name":"mdi:harddisk-plus","tags":["hdd plus"]},{"name":"mdi:harddisk-remove","tags":["hdd remove"]},{"name":"mdi:hazard-lights","tags":["automotive","warning lights"]},{"name":"mdi:hdmi-port","tags":["video / movie","home automation"]},{"name":"mdi:head","tags":[]},{"name":"mdi:head-alert","tags":["alert / error"]},{"name":"mdi:head-alert-outline","tags":["alert / error"]},{"name":"mdi:head-check","tags":[]},{"name":"mdi:head-check-outline","tags":[]},{"name":"mdi:head-cog-outline","tags":["settings","psychology outline"]},{"name":"mdi:head-dots-horizontal","tags":["head thinking"]},{"name":"mdi:head-dots-horizontal-outline","tags":["head thinking outline"]},{"name":"mdi:head-flash","tags":["head ache"]},{"name":"mdi:head-flash-outline","tags":["head ache outline"]},{"name":"mdi:head-heart","tags":["head love"]},{"name":"mdi:head-heart-outline","tags":["head love outline"]},{"name":"mdi:head-lightbulb","tags":["head idea","head bulb"]},{"name":"mdi:head-lightbulb-outline","tags":["head idea outline","head bulb outline"]},{"name":"mdi:head-minus","tags":[]},{"name":"mdi:head-minus-outline","tags":[]},{"name":"mdi:head-outline","tags":[]},{"name":"mdi:head-plus","tags":[]},{"name":"mdi:head-plus-outline","tags":[]},{"name":"mdi:head-question","tags":[]},{"name":"mdi:head-question-outline","tags":[]},{"name":"mdi:head-remove","tags":[]},{"name":"mdi:head-remove-outline","tags":[]},{"name":"mdi:head-snowflake","tags":["head freeze","brain freeze"]},{"name":"mdi:head-snowflake-outline","tags":["head freeze outline","brain freeze outline"]},{"name":"mdi:head-sync","tags":["head reload","head refresh"]},{"name":"mdi:head-sync-outline","tags":["head reload outline","head refresh outline"]},{"name":"mdi:headphones-bluetooth","tags":[]},{"name":"mdi:headphones-off","tags":["audio","device / tech","music"]},{"name":"mdi:headphones-settings","tags":["audio","settings"]},{"name":"mdi:headset-dock","tags":["audio"]},{"name":"mdi:headset-off","tags":["audio","device / tech"]},{"name":"mdi:heart-box","tags":[]},{"name":"mdi:heart-box-outline","tags":[]},{"name":"mdi:heart-broken","tags":[]},{"name":"mdi:heart-broken-outline","tags":[]},{"name":"mdi:heart-circle","tags":[]},{"name":"mdi:heart-circle-outline","tags":[]},{"name":"mdi:heart-cog","tags":["settings"]},{"name":"mdi:heart-cog-outline","tags":["settings"]},{"name":"mdi:heart-flash","tags":["medical / hospital","aed","defibrillator"]},{"name":"mdi:heart-half","tags":["gaming / rpg"]},{"name":"mdi:heart-half-full","tags":["gaming / rpg"]},{"name":"mdi:heart-half-outline","tags":["gaming / rpg"]},{"name":"mdi:heart-minus","tags":[]},{"name":"mdi:heart-minus-outline","tags":[]},{"name":"mdi:heart-multiple","tags":["hearts"]},{"name":"mdi:heart-multiple-outline","tags":["hearts outline"]},{"name":"mdi:heart-off","tags":["medical / hospital"]},{"name":"mdi:heart-off-outline","tags":["medical / hospital"]},{"name":"mdi:heart-plus","tags":[]},{"name":"mdi:heart-plus-outline","tags":[]},{"name":"mdi:heart-remove","tags":[]},{"name":"mdi:heart-remove-outline","tags":[]},{"name":"mdi:heart-settings","tags":["settings"]},{"name":"mdi:heart-settings-outline","tags":["settings"]},{"name":"mdi:heat-wave","tags":["home automation","weather","agriculture","keep warm","warmth"]},{"name":"mdi:heating-coil","tags":["home automation","radiator coil","heated floor"]},{"name":"mdi:helicopter","tags":["transportation + flying"]},{"name":"mdi:help","tags":["question mark"]},{"name":"mdi:help-box","tags":["question mark box"]},{"name":"mdi:help-box-multiple","tags":["quiz","question box multiple"]},{"name":"mdi:help-box-multiple-outline","tags":["quiz outline","question box multiple outline"]},{"name":"mdi:help-box-outline","tags":["question box outline"]},{"name":"mdi:help-network","tags":["question network"]},{"name":"mdi:help-network-outline","tags":["question network outline"]},{"name":"mdi:help-rhombus","tags":["question mark rhombus"]},{"name":"mdi:help-rhombus-outline","tags":["question mark rhombus outline"]},{"name":"mdi:hexadecimal","tags":["developer / languages"]},{"name":"mdi:hexagon","tags":["shape"]},{"name":"mdi:hexagon-multiple","tags":["shape","hexagons"]},{"name":"mdi:hexagon-multiple-outline","tags":["nature"]},{"name":"mdi:hexagon-outline","tags":["shape"]},{"name":"mdi:hexagon-slice-1","tags":[]},{"name":"mdi:hexagon-slice-2","tags":[]},{"name":"mdi:hexagon-slice-3","tags":[]},{"name":"mdi:hexagon-slice-4","tags":[]},{"name":"mdi:hexagon-slice-5","tags":[]},{"name":"mdi:hexagon-slice-6","tags":[]},{"name":"mdi:hexagram","tags":["shape","holiday","star","christmas star"]},{"name":"mdi:hexagram-outline","tags":["shape","holiday","star outline","christmas star outline"]},{"name":"mdi:high-definition","tags":["video / movie","hd"]},{"name":"mdi:highway","tags":["transportation + road","autobahn","motorway"]},{"name":"mdi:hockey-puck","tags":["sport"]},{"name":"mdi:hololens","tags":["gaming / rpg"]},{"name":"mdi:home-account","tags":["account / user","home automation","home user","house account","house user"]},{"name":"mdi:home-alert","tags":["home automation","alert / error","home warning","house alert","house warning"]},{"name":"mdi:home-alert-outline","tags":["home automation","alert / error","house alert outline","home warning outline","house warning outline"]},{"name":"mdi:home-analytics","tags":["home automation","chart home","home chart","home report","house analytics","house chart"]},{"name":"mdi:home-automation","tags":["home automation","house automation","home wireless","house wireless","smart home","smart house"]},{"name":"mdi:home-battery","tags":["home automation","battery","home energy","home power","home electricity","house energy","house battery","house power"]},{"name":"mdi:home-battery-outline","tags":["home automation","battery","home energy outline","home power outline","home electricity outline","house battery outline","house power outline","house energy outline"]},{"name":"mdi:home-circle","tags":["home automation","house circle"]},{"name":"mdi:home-circle-outline","tags":["home automation","house circle outline"]},{"name":"mdi:home-clock","tags":["home automation","date / time","home time","home schedule","house time","house clock","house schedule"]},{"name":"mdi:home-clock-outline","tags":["home automation","date / time","home time outline","home schedule outline","house clock outline","house time outline","house schedule outline"]},{"name":"mdi:home-edit","tags":["home automation","edit / modify","house edit"]},{"name":"mdi:home-edit-outline","tags":["home automation","edit / modify","house edit outline"]},{"name":"mdi:home-export-outline","tags":["home automation","house export outline"]},{"name":"mdi:home-floor-0","tags":["home automation","house floor 0","home floor zero","house floor zero"]},{"name":"mdi:home-floor-1","tags":["home automation","house floor 1","home floor one","house floor one","home floor first","house floor first"]},{"name":"mdi:home-floor-2","tags":["home automation","house floor 2","home floor two","house floor two","home floor second","house floor second"]},{"name":"mdi:home-floor-3","tags":["home automation","house floor 3","home floor three","house floor three","home floor third","house floor third"]},{"name":"mdi:home-floor-a","tags":["home automation","home floor attic","house floor a","house floor attic"]},{"name":"mdi:home-floor-b","tags":["home automation","home floor basement","house floor b","house floor basement"]},{"name":"mdi:home-floor-g","tags":["home automation","home floor ground","house floor g","house floor ground"]},{"name":"mdi:home-floor-l","tags":["home automation","home floor loft","home floor lower","house floor l","house floor loft","house floor lower"]},{"name":"mdi:home-floor-negative-1","tags":["home automation","house floor negative 1","home floor negative one","home floor minus 1","home floor minus one","house floor negative one","house floor minus 1","house floor minus one"]},{"name":"mdi:home-group","tags":["home automation","house group","neighbourhood","estate","housing estate"]},{"name":"mdi:home-group-minus","tags":["home automation","house group minus"]},{"name":"mdi:home-group-plus","tags":["home automation","house group plus","home group add","house group add"]},{"name":"mdi:home-group-remove","tags":["home automation","house group remove"]},{"name":"mdi:home-import-outline","tags":["home automation","house import outline"]},{"name":"mdi:home-lightbulb","tags":["home automation","home bulb","house lightbulb","house bulb"]},{"name":"mdi:home-lightbulb-outline","tags":["home automation","home bulb outline","house lightbulb outline","house bulb outline"]},{"name":"mdi:home-lightning-bolt","tags":["home automation","home energy","home power","home electricity","home flash","house lightning bolt","house flash"]},{"name":"mdi:home-lightning-bolt-outline","tags":["home automation","home energy","home power","home electricity","home flash","house lightning bolt outline","house flash outline"]},{"name":"mdi:home-lock","tags":["home automation","lock","house lock","home secure","house secure"]},{"name":"mdi:home-lock-open","tags":["home automation","lock","house lock open"]},{"name":"mdi:home-map-marker","tags":["home automation","navigation","house map marker","home location"]},{"name":"mdi:home-minus","tags":["home automation","house minus"]},{"name":"mdi:home-minus-outline","tags":["home automation","house minus outline"]},{"name":"mdi:home-modern","tags":["home automation","house modern"]},{"name":"mdi:home-off","tags":["home automation","house off"]},{"name":"mdi:home-off-outline","tags":["home automation","house off outline"]},{"name":"mdi:home-percent","tags":[]},{"name":"mdi:home-percent-outline","tags":["home automation"]},{"name":"mdi:home-plus","tags":["home automation","home add","house plus","house add"]},{"name":"mdi:home-plus-outline","tags":["home automation","house plus outline","house add outline"]},{"name":"mdi:home-remove","tags":["home automation","house remove"]},{"name":"mdi:home-remove-outline","tags":["home automation","house remove outline"]},{"name":"mdi:home-roof","tags":["home automation","home chimney","home attic","house roof","house attic","house chimney"]},{"name":"mdi:home-search","tags":["home automation","house search","home find","house find"]},{"name":"mdi:home-search-outline","tags":["home automation","house search outline","home find outline","house find outline"]},{"name":"mdi:home-silo","tags":["home automation","agriculture","farm house","farm home"]},{"name":"mdi:home-silo-outline","tags":["agriculture","home automation","farm house outline","farm home outline"]},{"name":"mdi:home-sound-in","tags":["home automation"]},{"name":"mdi:home-sound-in-outline","tags":["home automation"]},{"name":"mdi:home-sound-out","tags":["home automation"]},{"name":"mdi:home-sound-out-outline","tags":["home automation"]},{"name":"mdi:home-switch","tags":["home automation","home swap","house switch","house swap"]},{"name":"mdi:home-switch-outline","tags":["home automation","home swap outline","house swap outline","house switch outline"]},{"name":"mdi:home-thermometer","tags":["home automation","home climate","home temperature","house thermometer","house climate","house temperature"]},{"name":"mdi:home-thermometer-outline","tags":["home automation","home climate outline","home temperature outline","house thermometer outline","house climate outline","house temperature outline"]},{"name":"mdi:hook","tags":[]},{"name":"mdi:hook-off","tags":[]},{"name":"mdi:hoop-house","tags":["agriculture","home automation","green house"]},{"name":"mdi:hops","tags":["food / drink","agriculture"]},{"name":"mdi:horizontal-rotate-clockwise","tags":[]},{"name":"mdi:horizontal-rotate-counterclockwise","tags":[]},{"name":"mdi:horse","tags":["transportation + other","animal","agriculture","equestrian"]},{"name":"mdi:horse-human","tags":["transportation + other","agriculture","people / family","horseback riding","horse riding","equestrian"]},{"name":"mdi:horse-variant","tags":["animal","agriculture","equestrian variant"]},{"name":"mdi:horse-variant-fast","tags":["animal","agriculture"]},{"name":"mdi:horseshoe","tags":["sport","agriculture","luck"]},{"name":"mdi:hospital","tags":["medical / hospital","swiss cross","dispensary"]},{"name":"mdi:hospital-box-outline","tags":["medical / hospital","swiss cross box outline","dispensary box outline"]},{"name":"mdi:hospital-building","tags":["places","medical / hospital"]},{"name":"mdi:hospital-marker","tags":["medical / hospital","navigation","hospital location"]},{"name":"mdi:hours-24","tags":["date / time"]},{"name":"mdi:hubspot","tags":[]},{"name":"mdi:human-baby-changing-table","tags":["people / family","medical / hospital"]},{"name":"mdi:human-capacity-increase","tags":["account / user","transportation + other","people / family"]},{"name":"mdi:human-child","tags":["people / family"]},{"name":"mdi:human-dolly","tags":["people / family","human hand truck","human trolley"]},{"name":"mdi:human-edit","tags":["people / family","edit / modify"]},{"name":"mdi:human-female-boy","tags":["people / family","mother","mom","woman child","mum"]},{"name":"mdi:human-female-dance","tags":["people / family","sport","ballet"]},{"name":"mdi:human-female-female","tags":["people / family","woman woman","women"]},{"name":"mdi:human-female-girl","tags":["people / family","mother","mom","woman child","mum"]},{"name":"mdi:human-male-board","tags":["people / family","teacher","teaching","lecture","college","blackboard","whiteboard","human man board"]},{"name":"mdi:human-male-board-poll","tags":["people / family","teach poll"]},{"name":"mdi:human-male-boy","tags":["people / family","father","dad","man child"]},{"name":"mdi:human-male-child","tags":["people / family"]},{"name":"mdi:human-male-girl","tags":["people / family","father","dad","man child"]},{"name":"mdi:human-male-height","tags":["medical / hospital","people / family"]},{"name":"mdi:human-male-height-variant","tags":["medical / hospital","people / family"]},{"name":"mdi:human-male-male","tags":["people / family","man man","men"]},{"name":"mdi:human-non-binary","tags":["people / family","human genderless","human transgender"]},{"name":"mdi:human-queue","tags":["people / family","human line"]},{"name":"mdi:human-wheelchair","tags":["people / family","medical / hospital","human accessible"]},{"name":"mdi:human-white-cane","tags":["people / family","medical / hospital","human blind"]},{"name":"mdi:hvac-off","tags":["home automation","heating off","ventilation off","air conditioning off"]},{"name":"mdi:hydraulic-oil-level","tags":["automotive"]},{"name":"mdi:hydraulic-oil-temperature","tags":["automotive"]},{"name":"mdi:hydro-power","tags":["device / tech","agriculture","hydraulic turbine","water turbine","watermill"]},{"name":"mdi:hydrogen-station","tags":["automotive"]},{"name":"mdi:ice-cream-off","tags":["food / drink"]},{"name":"mdi:ice-pop","tags":["food / drink","popsicle"]},{"name":"mdi:id-card","tags":[]},{"name":"mdi:identifier","tags":["developer / languages","key"]},{"name":"mdi:ideogram-cjk","tags":["alpha / numeric","ideogram chinese japanese korean","writing system cjk"]},{"name":"mdi:ideogram-cjk-variant","tags":["alpha / numeric","ideogram chinese japanese korean variant","writing system cjk variant"]},{"name":"mdi:image-area","tags":[]},{"name":"mdi:image-area-close","tags":[]},{"name":"mdi:image-broken","tags":[]},{"name":"mdi:image-check","tags":[]},{"name":"mdi:image-check-outline","tags":[]},{"name":"mdi:image-edit","tags":["edit / modify"]},{"name":"mdi:image-edit-outline","tags":["edit / modify"]},{"name":"mdi:image-filter-hdr-outline","tags":["photography","nature","mountain outline","landscape outline"]},{"name":"mdi:image-lock","tags":["lock","photography","image secure"]},{"name":"mdi:image-lock-outline","tags":["photography","lock","image secure outline"]},{"name":"mdi:image-marker","tags":["navigation","image location"]},{"name":"mdi:image-marker-outline","tags":["navigation","image location outline"]},{"name":"mdi:image-minus","tags":[]},{"name":"mdi:image-minus-outline","tags":[]},{"name":"mdi:image-move","tags":[]},{"name":"mdi:image-off","tags":[]},{"name":"mdi:image-off-outline","tags":[]},{"name":"mdi:image-outline","tags":[]},{"name":"mdi:image-plus","tags":["image add"]},{"name":"mdi:image-plus-outline","tags":["image add outline"]},{"name":"mdi:image-refresh","tags":["photography"]},{"name":"mdi:image-refresh-outline","tags":["photography"]},{"name":"mdi:image-remove","tags":[]},{"name":"mdi:image-remove-outline","tags":[]},{"name":"mdi:image-sync","tags":["photography"]},{"name":"mdi:image-sync-outline","tags":["photography"]},{"name":"mdi:import","tags":["input"]},{"name":"mdi:inbox-arrow-down-outline","tags":[]},{"name":"mdi:inbox-arrow-up","tags":["move from inbox"]},{"name":"mdi:inbox-arrow-up-outline","tags":[]},{"name":"mdi:inbox-full","tags":[]},{"name":"mdi:inbox-full-outline","tags":[]},{"name":"mdi:inbox-outline","tags":[]},{"name":"mdi:inbox-remove","tags":[]},{"name":"mdi:inbox-remove-outline","tags":[]},{"name":"mdi:incognito","tags":["anonymous","spy"]},{"name":"mdi:incognito-circle-off","tags":["anonymous circle off","spy circle off"]},{"name":"mdi:incognito-off","tags":["spy off","anonymous off"]},{"name":"mdi:induction","tags":["home automation","automotive","ignition"]},{"name":"mdi:infinity","tags":["math"]},{"name":"mdi:information-box","tags":["settings","info box"]},{"name":"mdi:information-box-outline","tags":["settings","info box outline"]},{"name":"mdi:information-off","tags":["info off","info circle off","information circle off"]},{"name":"mdi:information-off-outline","tags":["info circle off outline","information circle off outline","information off outline","info off outline"]},{"name":"mdi:information-slab-box","tags":["settings","info slab box"]},{"name":"mdi:information-slab-box-outline","tags":["settings","info slab box outline"]},{"name":"mdi:information-slab-circle","tags":["settings","info slab circle"]},{"name":"mdi:information-slab-circle-outline","tags":["settings","info slab circle outline"]},{"name":"mdi:information-slab-symbol","tags":["settings","info slab symbol"]},{"name":"mdi:information-symbol","tags":["settings","info symbol"]},{"name":"mdi:information-variant","tags":["info variant","about variant","information serif symbol","info variant symbol"]},{"name":"mdi:information-variant-box","tags":["settings","info variant box","information serif box","info serif box"]},{"name":"mdi:information-variant-box-outline","tags":["settings","info variant box outline","information serif box outline","info serif box outline"]},{"name":"mdi:information-variant-circle","tags":["settings","information serif circle","info serif circle","info variant circle"]},{"name":"mdi:information-variant-circle-outline","tags":["settings","information serif circle outline","info variant circle outline","info serif circle outline"]},{"name":"mdi:instrument-triangle","tags":["music","dinner bell"]},{"name":"mdi:integrated-circuit-chip","tags":["banking","icc","chip"]},{"name":"mdi:ip","tags":["internet protocol"]},{"name":"mdi:ip-network","tags":[]},{"name":"mdi:ip-network-outline","tags":[]},{"name":"mdi:ip-outline","tags":["internet protocol outline"]},{"name":"mdi:ipod","tags":["apple ipod"]},{"name":"mdi:iron-board","tags":["home automation","clothing"]},{"name":"mdi:island","tags":["places"]},{"name":"mdi:iv-bag","tags":["medical / hospital"]},{"name":"mdi:jeepney","tags":["transportation + road"]},{"name":"mdi:jellyfish","tags":["animal"]},{"name":"mdi:jellyfish-outline","tags":["animal"]},{"name":"mdi:jump-rope","tags":["sport"]},{"name":"mdi:kangaroo","tags":["animal","marsupial"]},{"name":"mdi:keg","tags":["food / drink"]},{"name":"mdi:kettle","tags":["home automation","food / drink","tea kettle","kettle full","tea kettle full"]},{"name":"mdi:kettle-alert","tags":["home automation","alert / error","food / drink","tea kettle alert","kettle full alert","tea kettle full alert"]},{"name":"mdi:kettle-alert-outline","tags":["home automation","alert / error","food / drink","tea kettle alert outline","kettle empty alert","tea kettle empty alert"]},{"name":"mdi:kettle-off","tags":["home automation","food / drink","tea kettle off","tea kettle full off","kettle full off"]},{"name":"mdi:kettle-off-outline","tags":["home automation","food / drink","tea kettle off outline","kettle empty off","tea kettle empty off"]},{"name":"mdi:kettle-outline","tags":["food / drink","home automation","tea kettle outline","kettle empty","tea kettle empty"]},{"name":"mdi:kettle-pour-over","tags":[]},{"name":"mdi:kettle-steam","tags":["home automation","food / drink","tea kettle steam","kettle full steam","tea kettle full steam"]},{"name":"mdi:kettle-steam-outline","tags":["home automation","food / drink","tea kettle steam outline","kettle empty steam","tea kettle empty steam"]},{"name":"mdi:kettlebell","tags":["sport"]},{"name":"mdi:key-alert","tags":["alert / error"]},{"name":"mdi:key-alert-outline","tags":["alert / error"]},{"name":"mdi:key-arrow-right","tags":[]},{"name":"mdi:key-chain","tags":["automotive","home automation"]},{"name":"mdi:key-chain-variant","tags":["automotive","home automation"]},{"name":"mdi:key-change","tags":[]},{"name":"mdi:key-link","tags":["foreign key","sql foreign key"]},{"name":"mdi:key-minus","tags":[]},{"name":"mdi:key-plus","tags":["key add"]},{"name":"mdi:key-remove","tags":[]},{"name":"mdi:key-star","tags":["primary key","sql primary key","key favorite"]},{"name":"mdi:key-variant","tags":["automotive"]},{"name":"mdi:key-wireless","tags":[]},{"name":"mdi:keyboard-close-outline","tags":["keyboard hide outline"]},{"name":"mdi:keyboard-esc","tags":[]},{"name":"mdi:keyboard-f1","tags":[]},{"name":"mdi:keyboard-f10","tags":[]},{"name":"mdi:keyboard-f11","tags":[]},{"name":"mdi:keyboard-f12","tags":[]},{"name":"mdi:keyboard-f2","tags":[]},{"name":"mdi:keyboard-f3","tags":[]},{"name":"mdi:keyboard-f4","tags":[]},{"name":"mdi:keyboard-f5","tags":[]},{"name":"mdi:keyboard-f6","tags":[]},{"name":"mdi:keyboard-f7","tags":[]},{"name":"mdi:keyboard-f8","tags":[]},{"name":"mdi:keyboard-f9","tags":[]},{"name":"mdi:keyboard-off","tags":[]},{"name":"mdi:keyboard-off-outline","tags":[]},{"name":"mdi:keyboard-settings","tags":["settings"]},{"name":"mdi:keyboard-settings-outline","tags":["settings"]},{"name":"mdi:keyboard-space","tags":[]},{"name":"mdi:keyboard-tab-reverse","tags":[]},{"name":"mdi:keyboard-variant","tags":[]},{"name":"mdi:khanda","tags":["religion","sikh"]},{"name":"mdi:klingon","tags":[]},{"name":"mdi:knife","tags":["silverware knife","cutlery knife"]},{"name":"mdi:knife-military","tags":["gaming / rpg","dagger"]},{"name":"mdi:knob","tags":["audio","volume knob","volume control","dial","tuner","switch","adjuster"]},{"name":"mdi:koala","tags":["animal","marsupial","emoji koala","emoticon koala"]},{"name":"mdi:label-multiple","tags":[]},{"name":"mdi:label-multiple-outline","tags":[]},{"name":"mdi:label-percent","tags":[]},{"name":"mdi:label-percent-outline","tags":[]},{"name":"mdi:ladder","tags":["hardware / tools"]},{"name":"mdi:lambda","tags":["gaming / rpg","math"]},{"name":"mdi:lamp","tags":["home automation"]},{"name":"mdi:lamp-outline","tags":["home automation"]},{"name":"mdi:lamps","tags":["home automation","lights"]},{"name":"mdi:lamps-outline","tags":["home automation","lights outline"]},{"name":"mdi:lan","tags":["local area network"]},{"name":"mdi:lan-check","tags":[]},{"name":"mdi:lan-connect","tags":["local area network connect"]},{"name":"mdi:lan-disconnect","tags":["local area network disconnect"]},{"name":"mdi:lan-pending","tags":["local area network pending"]},{"name":"mdi:land-fields","tags":["agriculture"]},{"name":"mdi:land-plots","tags":["agriculture"]},{"name":"mdi:land-plots-circle","tags":["agriculture"]},{"name":"mdi:land-plots-circle-variant","tags":["agriculture"]},{"name":"mdi:land-plots-marker","tags":["agriculture"]},{"name":"mdi:land-rows-horizontal","tags":["agriculture"]},{"name":"mdi:land-rows-vertical","tags":["agriculture"]},{"name":"mdi:laptop-account","tags":["account / user","device / tech","teleconference","virtual meeting","video chat"]},{"name":"mdi:laptop-off","tags":["device / tech"]},{"name":"mdi:lasso","tags":[]},{"name":"mdi:latitude","tags":["navigation","geographic information system"]},{"name":"mdi:lava-lamp","tags":["home automation"]},{"name":"mdi:layers-edit","tags":["geographic information system","edit / modify"]},{"name":"mdi:layers-minus","tags":["geographic information system"]},{"name":"mdi:layers-plus","tags":["geographic information system"]},{"name":"mdi:layers-remove","tags":["geographic information system"]},{"name":"mdi:layers-search","tags":["geographic information system"]},{"name":"mdi:layers-search-outline","tags":["geographic information system"]},{"name":"mdi:layers-triple","tags":[]},{"name":"mdi:layers-triple-outline","tags":[]},{"name":"mdi:leaf","tags":["nature","food / drink","agriculture"]},{"name":"mdi:leaf-circle","tags":["nature","agriculture","green circle","organic"]},{"name":"mdi:leaf-circle-outline","tags":["agriculture","nature","green circle outline","organic outline"]},{"name":"mdi:leaf-maple","tags":["nature"]},{"name":"mdi:leaf-maple-off","tags":["nature"]},{"name":"mdi:leaf-off","tags":["nature","food / drink","agriculture"]},{"name":"mdi:lectern","tags":["podium","dais","rostrum","lecturn"]},{"name":"mdi:led-off","tags":["home automation"]},{"name":"mdi:led-on","tags":["home automation"]},{"name":"mdi:led-outline","tags":["home automation"]},{"name":"mdi:led-strip","tags":["home automation","light strip"]},{"name":"mdi:led-strip-variant","tags":["home automation","light strip variant"]},{"name":"mdi:led-strip-variant-off","tags":["home automation","light strip variant off"]},{"name":"mdi:led-variant-off","tags":["home automation"]},{"name":"mdi:led-variant-on","tags":["home automation"]},{"name":"mdi:led-variant-outline","tags":["home automation"]},{"name":"mdi:leek","tags":["food / drink"]},{"name":"mdi:less-than","tags":["math"]},{"name":"mdi:less-than-or-equal","tags":["math"]},{"name":"mdi:library-outline","tags":["places","local library outline"]},{"name":"mdi:lifebuoy","tags":["transportation + water","life preserver","support","help","overboard"]},{"name":"mdi:light-flood-down","tags":["home automation","floodlight down"]},{"name":"mdi:light-flood-up","tags":["home automation","floodlight up"]},{"name":"mdi:light-recessed","tags":["home automation","can light","pot light","high hat light","hi hat light","downlight"]},{"name":"mdi:light-switch","tags":["home automation","toggle switch","rocker switch"]},{"name":"mdi:light-switch-off","tags":["home automation","toggle switch off","rocker switch off"]},{"name":"mdi:lightbulb-alert","tags":["home automation","alert / error","lightbulb error"]},{"name":"mdi:lightbulb-alert-outline","tags":["home automation","alert / error","lightbulb error outline"]},{"name":"mdi:lightbulb-auto","tags":["home automation","lightbulb automatic","lightbulb motion"]},{"name":"mdi:lightbulb-auto-outline","tags":["home automation","lightbulb automatic outline","lightbulb motion outline"]},{"name":"mdi:lightbulb-cfl","tags":["home automation","bulb cfl"]},{"name":"mdi:lightbulb-cfl-off","tags":["home automation","bulb cfl off"]},{"name":"mdi:lightbulb-cfl-spiral","tags":["home automation","bulb cfl spiral"]},{"name":"mdi:lightbulb-cfl-spiral-off","tags":["home automation","bulb cfl spiral off"]},{"name":"mdi:lightbulb-fluorescent-tube","tags":["home automation"]},{"name":"mdi:lightbulb-fluorescent-tube-outline","tags":["home automation"]},{"name":"mdi:lightbulb-group","tags":["home automation","bulb group"]},{"name":"mdi:lightbulb-group-off","tags":["home automation","bulb group off"]},{"name":"mdi:lightbulb-group-off-outline","tags":["home automation","bulb group off outline"]},{"name":"mdi:lightbulb-group-outline","tags":["home automation","bulb group outline"]},{"name":"mdi:lightbulb-multiple","tags":["home automation","lightbulbs","bulb multiple","bulbs"]},{"name":"mdi:lightbulb-multiple-off","tags":["home automation","lightbulbs off","bulb multiple off","bulbs off"]},{"name":"mdi:lightbulb-multiple-off-outline","tags":["home automation","lightbulbs off outline","bulb multiple off outline","bulbs off outline"]},{"name":"mdi:lightbulb-multiple-outline","tags":["home automation","lightbulbs outline","bulb multiple outline","bulbs outline"]},{"name":"mdi:lightbulb-night","tags":["home automation","night light","nite light","lightbulb moon star"]},{"name":"mdi:lightbulb-night-outline","tags":["home automation","night light outline","nite light outline","lightbulb moon star outline"]},{"name":"mdi:lightbulb-off","tags":["home automation","bulb off"]},{"name":"mdi:lightbulb-off-outline","tags":["home automation","bulb off outline"]},{"name":"mdi:lightbulb-on","tags":["home automation","idea","bulb on","lightbulb dimmer 100"]},{"name":"mdi:lightbulb-on-10","tags":["home automation","lightbulb dimmer 10"]},{"name":"mdi:lightbulb-on-20","tags":["home automation","lightbulb dimmer 20"]},{"name":"mdi:lightbulb-on-30","tags":["home automation","lightbulb dimmer 30"]},{"name":"mdi:lightbulb-on-40","tags":["home automation","lightbulb dimmer 40"]},{"name":"mdi:lightbulb-on-50","tags":["home automation","lightbulb dimmer 50"]},{"name":"mdi:lightbulb-on-60","tags":["home automation","lightbulb dimmer 60"]},{"name":"mdi:lightbulb-on-70","tags":["home automation","lightbulb dimmer 70"]},{"name":"mdi:lightbulb-on-80","tags":["home automation","lightbulb dimmer 80"]},{"name":"mdi:lightbulb-on-90","tags":["home automation","lightbulb dimmer 90"]},{"name":"mdi:lightbulb-on-outline","tags":["home automation","idea","bulb on outline"]},{"name":"mdi:lightbulb-outline","tags":["home automation","idea","bulb outline"]},{"name":"mdi:lightbulb-question","tags":["home automation","lightbulb help"]},{"name":"mdi:lightbulb-question-outline","tags":["home automation","lightbulb help outline"]},{"name":"mdi:lightbulb-spot","tags":["home automation","lightbulb halogen","lightbulb gu10"]},{"name":"mdi:lightbulb-spot-off","tags":["home automation","lightbulb halogen off","lightbulb gu10 off"]},{"name":"mdi:lightbulb-variant","tags":["home automation","lightbulb edison","lightbulb filament"]},{"name":"mdi:lightbulb-variant-outline","tags":["home automation","lightbulb edison outline","lightbulb filament outline"]},{"name":"mdi:lighthouse","tags":["beacon"]},{"name":"mdi:lighthouse-on","tags":["beacon"]},{"name":"mdi:lightning-bolt","tags":["home automation","weather","thunder","storm","energy","electricity"]},{"name":"mdi:lightning-bolt-outline","tags":["home automation","weather","thunder outline","storm outline","energy outline","electricity outline"]},{"name":"mdi:line-scan","tags":[]},{"name":"mdi:lingerie","tags":["clothing","underwear","bra","panties"]},{"name":"mdi:link-box","tags":[]},{"name":"mdi:link-box-outline","tags":[]},{"name":"mdi:link-box-variant","tags":[]},{"name":"mdi:link-box-variant-outline","tags":[]},{"name":"mdi:link-lock","tags":["lock","block chain"]},{"name":"mdi:link-variant","tags":[]},{"name":"mdi:link-variant-minus","tags":[]},{"name":"mdi:link-variant-off","tags":[]},{"name":"mdi:link-variant-plus","tags":[]},{"name":"mdi:link-variant-remove","tags":[]},{"name":"mdi:lipstick","tags":["health / beauty"]},{"name":"mdi:liquid-spot","tags":["automotive","medical / hospital","ink spot","puddle","water","blood","spill","oil","dirty"]},{"name":"mdi:list-box","tags":["form"]},{"name":"mdi:list-box-outline","tags":["form outline"]},{"name":"mdi:loading","tags":[]},{"name":"mdi:location-enter","tags":["home automation","presence enter"]},{"name":"mdi:location-exit","tags":["home automation","presence exit"]},{"name":"mdi:lock-alert","tags":["lock","alert / error","home automation","lock warning","password alert","encryption alert","password warning","encryption warning"]},{"name":"mdi:lock-alert-outline","tags":["home automation","alert / error","lock","lock warning outline","password alert outline","encryption alert outline","password warning outline","encryption warning outline"]},{"name":"mdi:lock-check","tags":["lock","password check","password secure","encryption check","encryption secure","password verified","encryption verified"]},{"name":"mdi:lock-check-outline","tags":["lock","password check outline","password secure outline","encryption check outline","encryption secure outline","password verified outline","encryption verified outline"]},{"name":"mdi:lock-minus","tags":["lock","password minus","encryption minus"]},{"name":"mdi:lock-minus-outline","tags":["lock","password minus outline","encryption minus"]},{"name":"mdi:lock-off","tags":["lock","password off","not protected","unsecure","encryption off"]},{"name":"mdi:lock-off-outline","tags":["lock","password off outline","unsecure outline","not protected outline","encryption off outline"]},{"name":"mdi:lock-open-alert","tags":["alert / error","home automation","lock","unlocked alert","decrypted alert","lock open warning","unlocked warning","decrypted warning"]},{"name":"mdi:lock-open-alert-outline","tags":["home automation","alert / error","lock","unlocked alert outline","lock open warning outline","decrypted alert outline","unlocked warning outline","decrypted warning outline"]},{"name":"mdi:lock-open-check","tags":["lock","unlocked check","decrypted check"]},{"name":"mdi:lock-open-check-outline","tags":["lock","unlocked check outline","decrypted check outline"]},{"name":"mdi:lock-open-minus","tags":["lock","unlocked minus","decrypted minus"]},{"name":"mdi:lock-open-minus-outline","tags":["lock","unlocked minus outline","decrypted minus outline"]},{"name":"mdi:lock-open-plus","tags":["lock","unlocked plus","decrypted plus","lock open add","unlocked add","decrypted add"]},{"name":"mdi:lock-open-plus-outline","tags":["lock","unlocked plus outline","lock open add outline","unlocked add outline","decrypted plus outline","decrypted add outline"]},{"name":"mdi:lock-open-remove","tags":["lock","unlocked remove","decrypted remove"]},{"name":"mdi:lock-open-remove-outline","tags":["lock","unlocked remove outline","decrypted remove outline"]},{"name":"mdi:lock-open-variant","tags":["lock","home automation","unlocked variant","decrypted variant"]},{"name":"mdi:lock-open-variant-outline","tags":["lock","home automation","unlocked variant outline","decrypted variant outline"]},{"name":"mdi:lock-pattern","tags":[]},{"name":"mdi:lock-percent","tags":["lock rate"]},{"name":"mdi:lock-percent-open","tags":["lock rate open"]},{"name":"mdi:lock-percent-open-outline","tags":["lock rate open outline"]},{"name":"mdi:lock-percent-open-variant","tags":["lock rate open variant"]},{"name":"mdi:lock-percent-open-variant-outline","tags":["lock rate open variant outline"]},{"name":"mdi:lock-percent-outline","tags":["lock rate outline"]},{"name":"mdi:lock-plus","tags":["lock","enhanced encryption","lock add","encryption add","password add","password plus","encryption plus"]},{"name":"mdi:lock-plus-outline","tags":["lock","lock add outline","password plus outline","password add outline","encryption plus outline","encryption add outline"]},{"name":"mdi:lock-question","tags":["lock","forgot password","password question","encryption question"]},{"name":"mdi:lock-remove","tags":["lock","password remove","encryption remove"]},{"name":"mdi:lock-remove-outline","tags":["lock","password remove outline","encryption remove outline"]},{"name":"mdi:lock-smart","tags":["home automation"]},{"name":"mdi:locker","tags":[]},{"name":"mdi:locker-multiple","tags":["lockers"]},{"name":"mdi:login","tags":["log in","sign in"]},{"name":"mdi:login-variant","tags":["log in variant","sign in variant"]},{"name":"mdi:logout","tags":["log out","sign out"]},{"name":"mdi:logout-variant","tags":["log out variant","sign out variant"]},{"name":"mdi:longitude","tags":["navigation","geographic information system"]},{"name":"mdi:lotion","tags":["medical / hospital","health / beauty"]},{"name":"mdi:lotion-outline","tags":["medical / hospital","health / beauty"]},{"name":"mdi:lungs","tags":["medical / hospital"]},{"name":"mdi:mace","tags":["gaming / rpg"]},{"name":"mdi:magazine-pistol","tags":["ammunition pistol"]},{"name":"mdi:magazine-rifle","tags":["ammunition rifle"]},{"name":"mdi:magic-staff","tags":["gaming / rpg","staff shimmer","magic wand"]},{"name":"mdi:magnet","tags":[]},{"name":"mdi:magnet-on","tags":[]},{"name":"mdi:magnify-close","tags":[]},{"name":"mdi:magnify-expand","tags":["geographic information system","search expand"]},{"name":"mdi:magnify-minus","tags":["zoom out","search minus"]},{"name":"mdi:magnify-minus-cursor","tags":["zoom out cursor"]},{"name":"mdi:magnify-plus","tags":["zoom in","magnify add","search plus","search add"]},{"name":"mdi:magnify-plus-cursor","tags":["zoom in cursor","magnify add cursor"]},{"name":"mdi:magnify-remove-cursor","tags":[]},{"name":"mdi:magnify-remove-outline","tags":["geographic information system"]},{"name":"mdi:magnify-scan","tags":[]},{"name":"mdi:mail","tags":[]},{"name":"mdi:mailbox","tags":[]},{"name":"mdi:mailbox-open","tags":[]},{"name":"mdi:mailbox-open-outline","tags":[]},{"name":"mdi:mailbox-open-up","tags":[]},{"name":"mdi:mailbox-open-up-outline","tags":[]},{"name":"mdi:mailbox-outline","tags":[]},{"name":"mdi:mailbox-up","tags":[]},{"name":"mdi:mailbox-up-outline","tags":[]},{"name":"mdi:map-check","tags":["navigation","geographic information system","map tick"]},{"name":"mdi:map-check-outline","tags":["navigation","geographic information system","map tick outline"]},{"name":"mdi:map-clock","tags":["navigation","geographic information system","date / time","timezone"]},{"name":"mdi:map-clock-outline","tags":["navigation","geographic information system","date / time","timezone outline"]},{"name":"mdi:map-legend","tags":["navigation","geographic information system"]},{"name":"mdi:map-marker-alert","tags":["navigation","alert / error","geographic information system","location alert","location warning"]},{"name":"mdi:map-marker-alert-outline","tags":["navigation","alert / error","geographic information system","location alert outline","location warning outline"]},{"name":"mdi:map-marker-check-outline","tags":["navigation","geographic information system","location check outline","where to vote outline"]},{"name":"mdi:map-marker-distance","tags":["navigation","geographic information system","location distance"]},{"name":"mdi:map-marker-down","tags":["navigation","geographic information system","location down"]},{"name":"mdi:map-marker-left","tags":["navigation","geographic information system","location left"]},{"name":"mdi:map-marker-left-outline","tags":["navigation","geographic information system","location left outline"]},{"name":"mdi:map-marker-minus","tags":["navigation","geographic information system","location minus"]},{"name":"mdi:map-marker-minus-outline","tags":["geographic information system","navigation","location minus outline"]},{"name":"mdi:map-marker-multiple","tags":["navigation","geographic information system","map markers","location multiple","locations"]},{"name":"mdi:map-marker-multiple-outline","tags":["navigation","geographic information system","locations outline","location multiple outline","map markers outline"]},{"name":"mdi:map-marker-off","tags":["navigation","geographic information system","location off"]},{"name":"mdi:map-marker-off-outline","tags":["navigation","geographic information system","location off outline"]},{"name":"mdi:map-marker-path","tags":["navigation","geographic information system","location path"]},{"name":"mdi:map-marker-plus","tags":["navigation","geographic information system","location plus","map marker add","location add"]},{"name":"mdi:map-marker-plus-outline","tags":["geographic information system","navigation","map marker add outline","location plus outline","location add outline"]},{"name":"mdi:map-marker-radius","tags":["navigation","geographic information system","home automation","location radius"]},{"name":"mdi:map-marker-radius-outline","tags":["navigation","geographic information system","home automation","location radius outline"]},{"name":"mdi:map-marker-remove","tags":["navigation","geographic information system","location remove"]},{"name":"mdi:map-marker-remove-outline","tags":["geographic information system","navigation","location remove outline"]},{"name":"mdi:map-marker-remove-variant","tags":["navigation","geographic information system","location remove variant outline"]},{"name":"mdi:map-marker-right","tags":["navigation","geographic information system","location right"]},{"name":"mdi:map-marker-right-outline","tags":["navigation","geographic information system","location right outline"]},{"name":"mdi:map-marker-star","tags":["navigation","map marker favorite","location star","location favorite"]},{"name":"mdi:map-marker-star-outline","tags":["navigation","map marker favorite outline","location star outline","location favorite outline"]},{"name":"mdi:map-marker-up","tags":["navigation","geographic information system","location up"]},{"name":"mdi:map-minus","tags":["navigation","geographic information system"]},{"name":"mdi:map-plus","tags":["navigation","geographic information system","map add"]},{"name":"mdi:map-search","tags":["navigation","geographic information system"]},{"name":"mdi:map-search-outline","tags":["navigation","geographic information system"]},{"name":"mdi:margin","tags":[]},{"name":"mdi:marker-cancel","tags":["text / content / format"]},{"name":"mdi:math-compass","tags":["math","drawing / art","navigation","maths compass"]},{"name":"mdi:math-cos","tags":["math","math cosine","maths cos"]},{"name":"mdi:math-integral","tags":["math"]},{"name":"mdi:math-integral-box","tags":["math"]},{"name":"mdi:math-log","tags":["math"]},{"name":"mdi:math-norm","tags":["math","developer / languages","code or","parallel"]},{"name":"mdi:math-norm-box","tags":["math","developer / languages","code or box","parallel box"]},{"name":"mdi:math-sin","tags":["math","math sine","maths sin"]},{"name":"mdi:math-tan","tags":["math","math tangent","maths tan"]},{"name":"mdi:matrix","tags":[]},{"name":"mdi:medal","tags":["gaming / rpg","sport","award"]},{"name":"mdi:medal-outline","tags":["sport"]},{"name":"mdi:medical-bag","tags":["medical / hospital","first aid kit","medicine"]},{"name":"mdi:menorah","tags":["religion","holiday","candelabrum","candelabra","candle"]},{"name":"mdi:menorah-fire","tags":["religion","holiday","menorah flame","candle flame","candelabra flame","candelabra fire","candle fire","candelabrum fire","candelabrum flame"]},{"name":"mdi:menu-down-outline","tags":["arrow","caret down outline"]},{"name":"mdi:menu-left","tags":["arrow","arrow left"]},{"name":"mdi:menu-left-outline","tags":[]},{"name":"mdi:menu-right","tags":["arrow","arrow right"]},{"name":"mdi:menu-right-outline","tags":[]},{"name":"mdi:menu-swap","tags":["arrow"]},{"name":"mdi:menu-swap-outline","tags":["arrow"]},{"name":"mdi:menu-up-outline","tags":["arrow","caret up outline"]},{"name":"mdi:message-alert-outline","tags":["alert / error","announcement outline","feedback outline","message warning outline","sms failed outline"]},{"name":"mdi:message-arrow-left","tags":[]},{"name":"mdi:message-arrow-left-outline","tags":[]},{"name":"mdi:message-arrow-right","tags":[]},{"name":"mdi:message-arrow-right-outline","tags":[]},{"name":"mdi:message-check","tags":[]},{"name":"mdi:message-check-outline","tags":[]},{"name":"mdi:message-cog","tags":["settings"]},{"name":"mdi:message-cog-outline","tags":["settings"]},{"name":"mdi:message-fast","tags":[]},{"name":"mdi:message-fast-outline","tags":[]},{"name":"mdi:message-image-outline","tags":[]},{"name":"mdi:message-lock","tags":["lock","message secure"]},{"name":"mdi:message-lock-outline","tags":["lock"]},{"name":"mdi:message-minus","tags":[]},{"name":"mdi:message-minus-outline","tags":[]},{"name":"mdi:message-off","tags":[]},{"name":"mdi:message-off-outline","tags":[]},{"name":"mdi:message-plus","tags":["message add"]},{"name":"mdi:message-plus-outline","tags":[]},{"name":"mdi:message-processing-outline","tags":[]},{"name":"mdi:message-question","tags":[]},{"name":"mdi:message-question-outline","tags":[]},{"name":"mdi:message-reply-outline","tags":[]},{"name":"mdi:message-reply-text-outline","tags":[]},{"name":"mdi:message-settings","tags":["settings"]},{"name":"mdi:message-settings-outline","tags":["settings"]},{"name":"mdi:message-star","tags":[]},{"name":"mdi:message-star-outline","tags":[]},{"name":"mdi:message-text-clock","tags":["date / time"]},{"name":"mdi:message-text-clock-outline","tags":["date / time"]},{"name":"mdi:message-text-fast","tags":[]},{"name":"mdi:message-text-fast-outline","tags":[]},{"name":"mdi:message-text-lock","tags":["lock","message text secure"]},{"name":"mdi:message-text-lock-outline","tags":["lock"]},{"name":"mdi:message-text-outline","tags":[]},{"name":"mdi:metronome","tags":["music","tempo","bpm","beats per minute"]},{"name":"mdi:metronome-tick","tags":["music","tempo tick","bpm tick","beats per minute tick"]},{"name":"mdi:micro-sd","tags":[]},{"name":"mdi:microphone-message","tags":["tts","text to speech"]},{"name":"mdi:microphone-message-off","tags":["tts off","text to speech off"]},{"name":"mdi:microphone-minus","tags":["microphone remove"]},{"name":"mdi:microphone-plus","tags":["microphone add"]},{"name":"mdi:microphone-question","tags":["audio","music","microphone help"]},{"name":"mdi:microphone-question-outline","tags":["audio","music","microphone help outline"]},{"name":"mdi:microphone-variant","tags":["music"]},{"name":"mdi:microphone-variant-off","tags":["music"]},{"name":"mdi:microscope","tags":["science"]},{"name":"mdi:microsoft-xbox-controller-battery-unknown","tags":["battery","gaming / rpg","microsoft xbox gamepad battery unknown"]},{"name":"mdi:microwave","tags":["home automation","food / drink","microwave oven"]},{"name":"mdi:microwave-off","tags":["home automation"]},{"name":"mdi:middleware","tags":["arrow"]},{"name":"mdi:middleware-outline","tags":["arrow"]},{"name":"mdi:midi-port","tags":["music"]},{"name":"mdi:mine","tags":[]},{"name":"mdi:mini-sd","tags":[]},{"name":"mdi:minidisc","tags":[]},{"name":"mdi:minus-box-multiple","tags":["form","library minus"]},{"name":"mdi:minus-box-multiple-outline","tags":["form","library minus outline"]},{"name":"mdi:minus-circle-multiple","tags":["form","coins minus"]},{"name":"mdi:minus-circle-multiple-outline","tags":["form","coins minus outline"]},{"name":"mdi:minus-circle-off","tags":["do not disturb off","remove circle off","do not enter off"]},{"name":"mdi:minus-circle-off-outline","tags":["do not disturb off outline","remove circle off outline","do not enter off outline"]},{"name":"mdi:minus-network","tags":[]},{"name":"mdi:minus-network-outline","tags":[]},{"name":"mdi:minus-thick","tags":[]},{"name":"mdi:mirror","tags":["home automation"]},{"name":"mdi:mirror-rectangle","tags":["home automation"]},{"name":"mdi:mirror-variant","tags":["home automation"]},{"name":"mdi:mixed-reality","tags":[]},{"name":"mdi:molecule","tags":["science"]},{"name":"mdi:molecule-co","tags":["home automation","science","carbon monoxide","gas co"]},{"name":"mdi:molecule-co2","tags":["science","home automation","periodic table carbon dioxide","gas co2"]},{"name":"mdi:monitor-account","tags":["account / user","device / tech","teleconference","virtual meeting","video chat"]},{"name":"mdi:monitor-arrow-down","tags":["device / tech","monitor download"]},{"name":"mdi:monitor-arrow-down-variant","tags":["device / tech","monitor download"]},{"name":"mdi:monitor-dashboard","tags":["device / tech"]},{"name":"mdi:monitor-edit","tags":["edit / modify"]},{"name":"mdi:monitor-eye","tags":[]},{"name":"mdi:monitor-lock","tags":["device / tech","lock"]},{"name":"mdi:monitor-multiple","tags":["device / tech","monitors"]},{"name":"mdi:monitor-shimmer","tags":["device / tech","monitor clean"]},{"name":"mdi:monitor-small","tags":["device / tech","monitor crt"]},{"name":"mdi:monitor-speaker","tags":["device / tech"]},{"name":"mdi:monitor-speaker-off","tags":["device / tech"]},{"name":"mdi:monitor-star","tags":["device / tech","monitor favorite"]},{"name":"mdi:monitor-vertical","tags":[]},{"name":"mdi:moon-first-quarter","tags":["weather"]},{"name":"mdi:moon-full","tags":["weather"]},{"name":"mdi:moon-last-quarter","tags":["weather"]},{"name":"mdi:moon-new","tags":["weather"]},{"name":"mdi:moon-waning-crescent","tags":["weather"]},{"name":"mdi:moon-waning-gibbous","tags":["weather"]},{"name":"mdi:moon-waxing-crescent","tags":["weather"]},{"name":"mdi:moon-waxing-gibbous","tags":["weather"]},{"name":"mdi:mortar-pestle","tags":[]},{"name":"mdi:mother-heart","tags":["people / family"]},{"name":"mdi:mother-nurse","tags":["medical / hospital","people / family","breast feed"]},{"name":"mdi:motion-sensor","tags":["home automation","motion detector"]},{"name":"mdi:motion-sensor-off","tags":["home automation"]},{"name":"mdi:motorbike-electric","tags":["transportation + road","motorcycle electric"]},{"name":"mdi:motorbike-off","tags":["transportation + road","motorcycle off"]},{"name":"mdi:mouse-bluetooth","tags":[]},{"name":"mdi:mouse-move-down","tags":[]},{"name":"mdi:mouse-move-up","tags":[]},{"name":"mdi:mouse-move-vertical","tags":[]},{"name":"mdi:mouse-off","tags":[]},{"name":"mdi:mouse-variant","tags":[]},{"name":"mdi:mouse-variant-off","tags":[]},{"name":"mdi:move-resize","tags":[]},{"name":"mdi:move-resize-variant","tags":[]},{"name":"mdi:movie-check","tags":["video / movie","slate check","clapperboard check","film check"]},{"name":"mdi:movie-check-outline","tags":["video / movie","slate check outline","clapperboard check outline","film check outline"]},{"name":"mdi:movie-cog","tags":["video / movie","settings","slate cog","clapperboard cog","film cog"]},{"name":"mdi:movie-cog-outline","tags":["video / movie","settings","slate cog outline","clapperboard cog outline","film cog outline"]},{"name":"mdi:movie-edit","tags":["video / movie","edit / modify","slate edit","clapperboard edit","film edit"]},{"name":"mdi:movie-edit-outline","tags":["video / movie","edit / modify","slate edit outline","clapperboard edit outline","film edit outline"]},{"name":"mdi:movie-minus","tags":["video / movie","slate minus","clapperboard minus","film minus"]},{"name":"mdi:movie-minus-outline","tags":["video / movie","slate minus outline","clapperboard minus outline","film minus outline"]},{"name":"mdi:movie-off","tags":["video / movie","slate off","clapperboard off","film off"]},{"name":"mdi:movie-off-outline","tags":["video / movie","slate off outline","clapperboard off outline","film off outline"]},{"name":"mdi:movie-open","tags":["video / movie","slate open","clapperboard open","film open","movie creation"]},{"name":"mdi:movie-open-check","tags":["video / movie","slate open check","clapperboard open check","film open check"]},{"name":"mdi:movie-open-check-outline","tags":["video / movie","slate open check outline","clapperboard open check outline","film open check outline"]},{"name":"mdi:movie-open-cog","tags":["video / movie","settings","slate open cog","clapperboard open cog","film open cog"]},{"name":"mdi:movie-open-cog-outline","tags":["video / movie","settings","slate open cog outline","clapperboard open cog outline","film open cog outline"]},{"name":"mdi:movie-open-edit","tags":["video / movie","edit / modify","slate open edit","clapperboard open edit","film open edit"]},{"name":"mdi:movie-open-edit-outline","tags":["video / movie","edit / modify","slate open edit outline","clapperboard open edit outline","film open edit outline"]},{"name":"mdi:movie-open-minus","tags":["video / movie","slate open minus","clapperboard open minus","film open minus"]},{"name":"mdi:movie-open-minus-outline","tags":["video / movie","slate open minus outline","clapperboard open minus outline","film open minus outline"]},{"name":"mdi:movie-open-off","tags":["video / movie","slate open off","clapperboard open off","film open off"]},{"name":"mdi:movie-open-off-outline","tags":["video / movie","slate open off outline","clapperboard open off outline","film open off outline"]},{"name":"mdi:movie-open-outline","tags":["video / movie","slate open outline","clapperboard open outline","film open outline","movie creation"]},{"name":"mdi:movie-open-play","tags":["video / movie","slate open play","clapperboard open play","film open play"]},{"name":"mdi:movie-open-play-outline","tags":["video / movie","slate open play outline","clapperboard open play outline","film open play outline"]},{"name":"mdi:movie-open-plus","tags":["video / movie","clapperboard open plus","slate open plus","flim open plus"]},{"name":"mdi:movie-open-plus-outline","tags":["video / movie","slate open plus outline","clapperboard open plus outline","film open plus outline"]},{"name":"mdi:movie-open-remove","tags":["video / movie","slate open remove","clapperboard open remove","film open remove"]},{"name":"mdi:movie-open-remove-outline","tags":["video / movie","slate open remove outline","clapperboard open remove outline","film open remove outline"]},{"name":"mdi:movie-open-settings","tags":["video / movie","settings","slate open settings","clapperboard open settings","film open settings"]},{"name":"mdi:movie-open-settings-outline","tags":["video / movie","settings","slate open settings outline","clapperboard open settings outline","film open settings outline"]},{"name":"mdi:movie-open-star","tags":["video / movie","slate open star","clapperboard open star","film open star","movie open favorite"]},{"name":"mdi:movie-open-star-outline","tags":["video / movie","slate open star outline","clapperboard open star outline","film open star outline","movie open favorite outline"]},{"name":"mdi:movie-play","tags":["video / movie","slate play","clapperboard play","film play"]},{"name":"mdi:movie-play-outline","tags":["video / movie","slate play outline","clapperboard play outline","film play outline"]},{"name":"mdi:movie-plus","tags":["video / movie","slate plus","clapperboard plus","film plus"]},{"name":"mdi:movie-plus-outline","tags":["video / movie","slate plus outline","clapperboard plus outline","film plus outline"]},{"name":"mdi:movie-remove","tags":["video / movie","slate remove","clapperboard remove","film remove"]},{"name":"mdi:movie-remove-outline","tags":["video / movie","slate remove outline","clapperboard remove outline","film remove outline"]},{"name":"mdi:movie-roll","tags":["video / movie","film reel"]},{"name":"mdi:movie-search","tags":["video / movie"]},{"name":"mdi:movie-search-outline","tags":["video / movie"]},{"name":"mdi:movie-settings","tags":["video / movie","settings","slate settings","clapperboard settings","film settings"]},{"name":"mdi:movie-settings-outline","tags":["video / movie","settings","slate settings outline","clapperboard settings outline","film settings outline"]},{"name":"mdi:movie-star","tags":["video / movie","slate star","clapperboard star","film star","movie favorite"]},{"name":"mdi:movie-star-outline","tags":["video / movie","slate star outline","clapperboard star outline","film star outline","movie favorite outline"]},{"name":"mdi:mower","tags":["hardware / tools","home automation"]},{"name":"mdi:mower-bag","tags":["hardware / tools","home automation"]},{"name":"mdi:mower-bag-on","tags":["hardware / tools","home automation"]},{"name":"mdi:mower-on","tags":["hardware / tools","home automation"]},{"name":"mdi:muffin","tags":["food / drink"]},{"name":"mdi:multicast","tags":["multiplex","broadcast"]},{"name":"mdi:multimedia","tags":["audio","video / movie","photography","audio","video","image","music","movie","picture"]},{"name":"mdi:multiplication","tags":["math"]},{"name":"mdi:multiplication-box","tags":["math"]},{"name":"mdi:mushroom","tags":["nature","food / drink","agriculture","fungus"]},{"name":"mdi:mushroom-off","tags":["food / drink","nature","agriculture"]},{"name":"mdi:mushroom-off-outline","tags":["food / drink","nature","agriculture"]},{"name":"mdi:mushroom-outline","tags":["nature","food / drink","agriculture","fungus outline"]},{"name":"mdi:music","tags":["audio","music"]},{"name":"mdi:music-accidental-double-flat","tags":["music"]},{"name":"mdi:music-accidental-double-sharp","tags":["music"]},{"name":"mdi:music-accidental-flat","tags":["music"]},{"name":"mdi:music-accidental-natural","tags":["music"]},{"name":"mdi:music-accidental-sharp","tags":["music"]},{"name":"mdi:music-box","tags":["audio","music"]},{"name":"mdi:music-box-multiple-outline","tags":["music","library music outline"]},{"name":"mdi:music-box-outline","tags":["audio","music"]},{"name":"mdi:music-circle","tags":["audio","music","note circle"]},{"name":"mdi:music-circle-outline","tags":["music","audio","note circle outline"]},{"name":"mdi:music-clef-alto","tags":["music","music c clef","music clef tenor","music clef soprano","music clef baritone"]},{"name":"mdi:music-clef-bass","tags":["music","music f clef"]},{"name":"mdi:music-clef-treble","tags":["music","music g clef"]},{"name":"mdi:music-note","tags":["audio","music"]},{"name":"mdi:music-note-bluetooth","tags":["audio","music"]},{"name":"mdi:music-note-bluetooth-off","tags":["audio","music"]},{"name":"mdi:music-note-eighth","tags":["audio","music"]},{"name":"mdi:music-note-eighth-dotted","tags":["music"]},{"name":"mdi:music-note-half","tags":["audio","music"]},{"name":"mdi:music-note-half-dotted","tags":["music"]},{"name":"mdi:music-note-minus","tags":[]},{"name":"mdi:music-note-off","tags":["audio","music"]},{"name":"mdi:music-note-off-outline","tags":["music"]},{"name":"mdi:music-note-outline","tags":["music"]},{"name":"mdi:music-note-plus","tags":["audio","music","music note add"]},{"name":"mdi:music-note-quarter","tags":["audio","music"]},{"name":"mdi:music-note-quarter-dotted","tags":["music"]},{"name":"mdi:music-note-sixteenth","tags":["audio","music"]},{"name":"mdi:music-note-sixteenth-dotted","tags":["music"]},{"name":"mdi:music-note-whole","tags":["audio","music"]},{"name":"mdi:music-note-whole-dotted","tags":["music"]},{"name":"mdi:music-off","tags":["audio","music"]},{"name":"mdi:music-rest-eighth","tags":["music"]},{"name":"mdi:music-rest-half","tags":["music"]},{"name":"mdi:music-rest-quarter","tags":["music"]},{"name":"mdi:music-rest-sixteenth","tags":["music"]},{"name":"mdi:music-rest-whole","tags":["music"]},{"name":"mdi:nail","tags":["hardware / tools"]},{"name":"mdi:nas","tags":["network attached storage"]},{"name":"mdi:nature-outline","tags":["nature"]},{"name":"mdi:nature-people-outline","tags":["account / user","nature"]},{"name":"mdi:necklace","tags":["clothing"]},{"name":"mdi:needle","tags":["medical / hospital","syringe","injection","medicine","shot","drug","immunization","pharmaceutical"]},{"name":"mdi:needle-off","tags":["medical / hospital","syringe off","injection off","medicine off","shot off","drug off","immunization off","pharmaceutical off"]},{"name":"mdi:network","tags":[]},{"name":"mdi:network-off","tags":[]},{"name":"mdi:network-off-outline","tags":[]},{"name":"mdi:network-outline","tags":[]},{"name":"mdi:network-pos","tags":["banking","network point of sale","network cash box"]},{"name":"mdi:network-strength-1","tags":["cellphone / phone"]},{"name":"mdi:network-strength-1-alert","tags":["cellphone / phone","alert / error","network strength 1 warning"]},{"name":"mdi:network-strength-2","tags":["cellphone / phone"]},{"name":"mdi:network-strength-2-alert","tags":["cellphone / phone","alert / error","network strength 2 warning"]},{"name":"mdi:network-strength-3","tags":["cellphone / phone"]},{"name":"mdi:network-strength-3-alert","tags":["cellphone / phone","alert / error","network strength 3 warning"]},{"name":"mdi:network-strength-4","tags":["cellphone / phone"]},{"name":"mdi:network-strength-4-alert","tags":["cellphone / phone","alert / error","network strength 4 warning"]},{"name":"mdi:network-strength-4-cog","tags":["settings","network strength 4 settings","data settings"]},{"name":"mdi:network-strength-off","tags":["cellphone / phone"]},{"name":"mdi:network-strength-off-outline","tags":["cellphone / phone"]},{"name":"mdi:network-strength-outline","tags":["cellphone / phone","network strength 0"]},{"name":"mdi:newspaper-check","tags":[]},{"name":"mdi:newspaper-minus","tags":[]},{"name":"mdi:newspaper-plus","tags":[]},{"name":"mdi:newspaper-remove","tags":[]},{"name":"mdi:newspaper-variant-multiple","tags":[]},{"name":"mdi:newspaper-variant-multiple-outline","tags":[]},{"name":"mdi:nfc-search-variant","tags":[]},{"name":"mdi:nfc-tap","tags":["near field communication tap"]},{"name":"mdi:nfc-variant-off","tags":["home automation","near field communication off"]},{"name":"mdi:ninja","tags":[]},{"name":"mdi:nintendo-game-boy","tags":["gaming / rpg"]},{"name":"mdi:not-equal","tags":[]},{"name":"mdi:not-equal-variant","tags":["math"]},{"name":"mdi:note","tags":["paper","sticky note","post it note"]},{"name":"mdi:note-alert","tags":["alert / error","paper alert","sticky note alert","post it note alert"]},{"name":"mdi:note-alert-outline","tags":["alert / error","paper alert outline","post it note alert outline","sticky note alert outline"]},{"name":"mdi:note-check","tags":["paper check","sticky note check","post it note check"]},{"name":"mdi:note-check-outline","tags":["paper check outline","sticky note check outline","post it note check outline"]},{"name":"mdi:note-edit","tags":["edit / modify","paper edit","sticky note edit","post it note edit"]},{"name":"mdi:note-edit-outline","tags":["edit / modify","paper edit outline","sticky note edit outline","post it note edit outline"]},{"name":"mdi:note-minus","tags":["paper minus","sticky note minus","post it note minus"]},{"name":"mdi:note-minus-outline","tags":["paper minus outline","sticky note minus outline","post it note minus outline"]},{"name":"mdi:note-multiple","tags":["notes","papers","sticky notes","post it notes"]},{"name":"mdi:note-multiple-outline","tags":["notes outline","papers outline","sticky notes outline","post it notes outline"]},{"name":"mdi:note-off","tags":["paper off","sticky note off","post it note off"]},{"name":"mdi:note-off-outline","tags":["paper off outline","sticky note off outline","post it note off outline"]},{"name":"mdi:note-outline","tags":["paper outline","sticky note outline","post it note outline"]},{"name":"mdi:note-plus","tags":["note add","paper plus","paper add","sticky note plus","sticky note add","post it note plus","post it note add"]},{"name":"mdi:note-plus-outline","tags":["note add outline","paper plus outline","paper add outline","sticky note plus outline","sticky note add outline","post it note plus outline","post it note add outline"]},{"name":"mdi:note-remove","tags":["paper remove","sticky note remove","post it note remove"]},{"name":"mdi:note-remove-outline","tags":[]},{"name":"mdi:note-search","tags":["paper search","sticky note search","post it note search"]},{"name":"mdi:note-search-outline","tags":["paper search outline","sticky note search outline","post it note search outline"]},{"name":"mdi:note-text","tags":["paper text","sticky note text","post it note text"]},{"name":"mdi:note-text-outline","tags":["paper text outline","sticky note text outline","post it note text outline"]},{"name":"mdi:notebook","tags":["journal","planner","diary"]},{"name":"mdi:notebook-check","tags":[]},{"name":"mdi:notebook-check-outline","tags":[]},{"name":"mdi:notebook-edit","tags":["edit / modify"]},{"name":"mdi:notebook-edit-outline","tags":["edit / modify"]},{"name":"mdi:notebook-heart","tags":["notebook favorite","notebook love"]},{"name":"mdi:notebook-heart-outline","tags":["notebook favorite outline","notebook love outline"]},{"name":"mdi:notebook-minus","tags":[]},{"name":"mdi:notebook-minus-outline","tags":[]},{"name":"mdi:notebook-multiple","tags":["journal multiple","planner multiple"]},{"name":"mdi:notebook-outline","tags":["journal outline","planner outline"]},{"name":"mdi:notebook-plus","tags":[]},{"name":"mdi:notebook-plus-outline","tags":[]},{"name":"mdi:notebook-remove","tags":[]},{"name":"mdi:notebook-remove-outline","tags":[]},{"name":"mdi:nuke","tags":["nuclear","atomic bomb"]},{"name":"mdi:null","tags":[]},{"name":"mdi:numeric","tags":["alpha / numeric","numbers","1 2 3","one two three","123"]},{"name":"mdi:numeric-0","tags":["alpha / numeric","number 0","numeric zero"]},{"name":"mdi:numeric-0-box-multiple","tags":["alpha / numeric"]},{"name":"mdi:numeric-0-circle","tags":["alpha / numeric","numeric zero circle","number 0 circle","number zero circle"]},{"name":"mdi:numeric-0-circle-outline","tags":["alpha / numeric","numeric zero circle outline","number 0 circle outline","number zero circle outline"]},{"name":"mdi:numeric-1","tags":["alpha / numeric","number 1","numeric one"]},{"name":"mdi:numeric-1-box-multiple","tags":["alpha / numeric"]},{"name":"mdi:numeric-1-circle","tags":["alpha / numeric","numeric one circle","number 1 circle","number one circle"]},{"name":"mdi:numeric-1-circle-outline","tags":["alpha / numeric","numeric one circle outline","number 1 circle outline","number one circle outline"]},{"name":"mdi:numeric-10","tags":["alpha / numeric"]},{"name":"mdi:numeric-10-box","tags":["alpha / numeric"]},{"name":"mdi:numeric-10-box-multiple","tags":["alpha / numeric"]},{"name":"mdi:numeric-10-box-multiple-outline","tags":["alpha / numeric"]},{"name":"mdi:numeric-10-box-outline","tags":["alpha / numeric"]},{"name":"mdi:numeric-10-circle","tags":["alpha / numeric"]},{"name":"mdi:numeric-10-circle-outline","tags":["alpha / numeric"]},{"name":"mdi:numeric-2","tags":["alpha / numeric","number 2","numeric two"]},{"name":"mdi:numeric-2-box-multiple","tags":["alpha / numeric"]},{"name":"mdi:numeric-2-circle","tags":["alpha / numeric","numeric two circle","number 2 circle","number two circle"]},{"name":"mdi:numeric-2-circle-outline","tags":["alpha / numeric","numeric two circle outline","number 2 circle outline","number two circle outline"]},{"name":"mdi:numeric-3","tags":["alpha / numeric","number 3","numeric three"]},{"name":"mdi:numeric-3-box-multiple","tags":["alpha / numeric"]},{"name":"mdi:numeric-3-circle","tags":["alpha / numeric","numeric three circle","number 3 circle","number three circle"]},{"name":"mdi:numeric-3-circle-outline","tags":["alpha / numeric","numeric three circle outline","number 3 circle outline","number three circle outline"]},{"name":"mdi:numeric-4","tags":["alpha / numeric","number 4","numeric four"]},{"name":"mdi:numeric-4-box-multiple","tags":["alpha / numeric"]},{"name":"mdi:numeric-4-circle","tags":["alpha / numeric","numeric four circle","number 4 circle","number four circle"]},{"name":"mdi:numeric-4-circle-outline","tags":["alpha / numeric","numeric four circle outline","number 4 circle outline","number four circle outline"]},{"name":"mdi:numeric-5","tags":["alpha / numeric","number 5","numeric five"]},{"name":"mdi:numeric-5-box-multiple","tags":["alpha / numeric"]},{"name":"mdi:numeric-5-circle","tags":["alpha / numeric","numeric five circle","number 5 circle","number five circle"]},{"name":"mdi:numeric-5-circle-outline","tags":["alpha / numeric","numeric five circle outline","number 5 circle outline","number five circle outline"]},{"name":"mdi:numeric-6","tags":["alpha / numeric","number 6","numeric six"]},{"name":"mdi:numeric-6-box-multiple","tags":["alpha / numeric"]},{"name":"mdi:numeric-6-circle","tags":["alpha / numeric","numeric six circle","number 6 circle","number six circle"]},{"name":"mdi:numeric-6-circle-outline","tags":["alpha / numeric","numeric six circle outline","number 6 circle outline","number six circle outline"]},{"name":"mdi:numeric-7","tags":["alpha / numeric","number 7","numeric seven"]},{"name":"mdi:numeric-7-box-multiple","tags":["alpha / numeric"]},{"name":"mdi:numeric-7-circle","tags":["alpha / numeric","numeric seven circle","number 7 circle","number seven circle"]},{"name":"mdi:numeric-7-circle-outline","tags":["alpha / numeric","numeric seven circle outline","number 7 circle outline","number seven circle outline"]},{"name":"mdi:numeric-8","tags":["alpha / numeric","number 8","numeric eight"]},{"name":"mdi:numeric-8-box-multiple","tags":["alpha / numeric"]},{"name":"mdi:numeric-8-circle","tags":["alpha / numeric","numeric eight circle","number 8 circle","number eight circle"]},{"name":"mdi:numeric-8-circle-outline","tags":["alpha / numeric","numeric eight circle outline","number 8 circle outline","number eight circle outline"]},{"name":"mdi:numeric-9","tags":["alpha / numeric","number 9","numeric nine"]},{"name":"mdi:numeric-9-box-multiple","tags":["alpha / numeric"]},{"name":"mdi:numeric-9-circle","tags":["alpha / numeric","numeric nine circle","number 9 circle","number nine circle"]},{"name":"mdi:numeric-9-circle-outline","tags":["alpha / numeric","numeric nine circle outline","number 9 circle outline","number nine circle outline"]},{"name":"mdi:numeric-9-plus","tags":["alpha / numeric"]},{"name":"mdi:numeric-9-plus-box-multiple","tags":["alpha / numeric"]},{"name":"mdi:numeric-9-plus-circle","tags":["alpha / numeric","numeric nine plus circle","number 9 plus circle","number nine plus circle"]},{"name":"mdi:numeric-9-plus-circle-outline","tags":["alpha / numeric","numeric nine plus circle outline","number 9 plus circle outline","number nine plus circle outline"]},{"name":"mdi:numeric-negative-1","tags":["alpha / numeric","decrement","minus one"]},{"name":"mdi:numeric-off","tags":["alpha / numeric","numbers off","123 off","one two three off"]},{"name":"mdi:numeric-positive-1","tags":["alpha / numeric","increment","plus one"]},{"name":"mdi:nut","tags":["hardware / tools"]},{"name":"mdi:nutrition","tags":["food / drink"]},{"name":"mdi:oar","tags":[]},{"name":"mdi:ocarina","tags":["music","gaming / rpg"]},{"name":"mdi:ocr","tags":["optical character recognition"]},{"name":"mdi:octagon","tags":["shape","transportation + road","stop"]},{"name":"mdi:octagon-outline","tags":["shape","transportation + road","stop outline"]},{"name":"mdi:octagram","tags":["shape","starburst"]},{"name":"mdi:octagram-edit","tags":["shape","starburst edit"]},{"name":"mdi:octagram-edit-outline","tags":["shape","starburst edit outline"]},{"name":"mdi:octagram-minus","tags":["shape","starburst plus"]},{"name":"mdi:octagram-minus-outline","tags":["shape","starburst minus outline"]},{"name":"mdi:octagram-outline","tags":["shape","starburst outline"]},{"name":"mdi:octagram-plus","tags":["shape","starburst plus"]},{"name":"mdi:octagram-plus-outline","tags":[]},{"name":"mdi:octahedron","tags":["shape"]},{"name":"mdi:octahedron-off","tags":["shape"]},{"name":"mdi:offer","tags":[]},{"name":"mdi:office-building","tags":["places"]},{"name":"mdi:office-building-cog","tags":["settings","places","office building settings"]},{"name":"mdi:office-building-cog-outline","tags":["settings","places","office building settings outline"]},{"name":"mdi:office-building-marker","tags":["navigation","places","office building location"]},{"name":"mdi:office-building-marker-outline","tags":["navigation","places","office building location outline"]},{"name":"mdi:office-building-minus","tags":[]},{"name":"mdi:office-building-minus-outline","tags":[]},{"name":"mdi:office-building-outline","tags":["places"]},{"name":"mdi:office-building-plus","tags":[]},{"name":"mdi:office-building-plus-outline","tags":[]},{"name":"mdi:office-building-remove","tags":[]},{"name":"mdi:office-building-remove-outline","tags":[]},{"name":"mdi:oil","tags":["automotive"]},{"name":"mdi:oil-lamp","tags":["wish","genie lamp"]},{"name":"mdi:oil-level","tags":["automotive"]},{"name":"mdi:oil-temperature","tags":["automotive"]},{"name":"mdi:om","tags":["religion","religion hindu","hinduism"]},{"name":"mdi:omega","tags":["ohm","electrical resistance"]},{"name":"mdi:one-up","tags":["gaming / rpg","1up","extra life"]},{"name":"mdi:orbit","tags":["science"]},{"name":"mdi:orbit-variant","tags":["photography","camera flip"]},{"name":"mdi:order-alphabetical-ascending","tags":["text / content / format"]},{"name":"mdi:order-alphabetical-descending","tags":["text / content / format"]},{"name":"mdi:order-bool-ascending","tags":["text / content / format"]},{"name":"mdi:order-bool-ascending-variant","tags":["text / content / format","order checkbox ascending"]},{"name":"mdi:order-bool-descending","tags":["text / content / format","order checkbox descending"]},{"name":"mdi:order-bool-descending-variant","tags":["text / content / format"]},{"name":"mdi:order-numeric-ascending","tags":["text / content / format"]},{"name":"mdi:order-numeric-descending","tags":["text / content / format"]},{"name":"mdi:ornament","tags":["holiday"]},{"name":"mdi:ornament-variant","tags":["holiday"]},{"name":"mdi:outdoor-lamp","tags":["home automation","outdoor light"]},{"name":"mdi:owl","tags":["animal","holiday"]},{"name":"mdi:pac-man","tags":["gaming / rpg"]},{"name":"mdi:package","tags":["box"]},{"name":"mdi:package-check","tags":["package delivered"]},{"name":"mdi:package-up","tags":["unarchive","box up","this side up"]},{"name":"mdi:package-variant","tags":["box variant"]},{"name":"mdi:package-variant-closed","tags":["box variant closed"]},{"name":"mdi:package-variant-closed-check","tags":["package variant closed delivered"]},{"name":"mdi:package-variant-closed-minus","tags":["package variant closed subtract","box variant closed minus","box variant closed subtract"]},{"name":"mdi:package-variant-closed-plus","tags":["box variant closed plus","package variant closed add","box variant closed add"]},{"name":"mdi:package-variant-closed-remove","tags":["box variant closed remove"]},{"name":"mdi:package-variant-minus","tags":["box variant minus","package variant subtract","box variant subtract"]},{"name":"mdi:package-variant-plus","tags":["box variant plus","package variant add","box variant add"]},{"name":"mdi:package-variant-remove","tags":["box variant remove"]},{"name":"mdi:page-layout-body","tags":[]},{"name":"mdi:page-layout-footer","tags":[]},{"name":"mdi:page-layout-header","tags":[]},{"name":"mdi:page-layout-header-footer","tags":["page layout marginals"]},{"name":"mdi:page-layout-sidebar-left","tags":[]},{"name":"mdi:page-layout-sidebar-right","tags":[]},{"name":"mdi:page-next","tags":["read more"]},{"name":"mdi:page-next-outline","tags":["read more outline"]},{"name":"mdi:page-previous","tags":[]},{"name":"mdi:page-previous-outline","tags":[]},{"name":"mdi:pail","tags":["bucket"]},{"name":"mdi:pail-minus","tags":["bucket minus"]},{"name":"mdi:pail-minus-outline","tags":["bucket minus outline"]},{"name":"mdi:pail-off","tags":["bucket off"]},{"name":"mdi:pail-off-outline","tags":["bucket off outline"]},{"name":"mdi:pail-outline","tags":["bucket outline"]},{"name":"mdi:pail-plus","tags":["bucket plus"]},{"name":"mdi:pail-plus-outline","tags":["bucket plus outline"]},{"name":"mdi:pail-remove","tags":["bucket remove"]},{"name":"mdi:pail-remove-outline","tags":["bucket remove outline"]},{"name":"mdi:palette-advanced","tags":["color","drawing / art","paint"]},{"name":"mdi:palette-swatch-variant","tags":["drawing / art","color","style","paint","material"]},{"name":"mdi:palm-tree","tags":["nature"]},{"name":"mdi:pan","tags":[]},{"name":"mdi:pan-bottom-left","tags":["pan down left"]},{"name":"mdi:pan-bottom-right","tags":["pan down right"]},{"name":"mdi:pan-down","tags":[]},{"name":"mdi:pan-horizontal","tags":[]},{"name":"mdi:pan-left","tags":[]},{"name":"mdi:pan-right","tags":[]},{"name":"mdi:pan-top-left","tags":["pan up left"]},{"name":"mdi:pan-top-right","tags":["pan up right"]},{"name":"mdi:pan-up","tags":[]},{"name":"mdi:pan-vertical","tags":[]},{"name":"mdi:panda","tags":["animal","emoji panda","emoticon panda"]},{"name":"mdi:paper-cut-vertical","tags":[]},{"name":"mdi:paper-roll","tags":["home automation","printer","lavatory roll","bathroom tissue","toilet paper","kitchen roll","paper towels","receipt roll"]},{"name":"mdi:paper-roll-outline","tags":["home automation","printer","lavatory roll outline","bathroom tissue outline","kitchen roll outline","paper towels outline","toilet paper outline","receipt roll outline"]},{"name":"mdi:paperclip-check","tags":["paperclip tick","attachment check","attachment tick"]},{"name":"mdi:paperclip-lock","tags":["lock","attachment lock"]},{"name":"mdi:paperclip-minus","tags":["paperclip subtract","attachment minus","attachment subtract"]},{"name":"mdi:paperclip-off","tags":["attachment off"]},{"name":"mdi:paperclip-plus","tags":["paperclip add","attachment plus","attachment add"]},{"name":"mdi:paperclip-remove","tags":["attachment remove"]},{"name":"mdi:parachute","tags":["transportation + flying"]},{"name":"mdi:parachute-outline","tags":["transportation + flying"]},{"name":"mdi:passport","tags":[]},{"name":"mdi:passport-biometric","tags":["passport electronic"]},{"name":"mdi:patio-heater","tags":["home automation"]},{"name":"mdi:pause-box","tags":["audio","music"]},{"name":"mdi:pause-box-outline","tags":["audio","music"]},{"name":"mdi:pause-octagon","tags":["stop pause"]},{"name":"mdi:pause-octagon-outline","tags":["stop pause outline"]},{"name":"mdi:paw","tags":["animal","nature","pets"]},{"name":"mdi:paw-off","tags":["animal"]},{"name":"mdi:paw-off-outline","tags":["animal"]},{"name":"mdi:paw-outline","tags":["animal"]},{"name":"mdi:peace","tags":[]},{"name":"mdi:peanut","tags":["food / drink","agriculture","allergen","food allergy"]},{"name":"mdi:peanut-off","tags":["food / drink","agriculture","allergen off","food allergy off"]},{"name":"mdi:peanut-off-outline","tags":["food / drink","agriculture","allergen off outline","food allergy off outline"]},{"name":"mdi:peanut-outline","tags":["food / drink","agriculture","allergen outline","food allergy outline"]},{"name":"mdi:pen","tags":["drawing / art"]},{"name":"mdi:pen-lock","tags":["lock"]},{"name":"mdi:pen-minus","tags":[]},{"name":"mdi:pen-off","tags":[]},{"name":"mdi:pen-plus","tags":["pen add"]},{"name":"mdi:pen-remove","tags":[]},{"name":"mdi:pencil-box","tags":["drawing / art","edit box"]},{"name":"mdi:pencil-box-multiple","tags":["edit / modify","library edit"]},{"name":"mdi:pencil-box-multiple-outline","tags":["edit / modify","library edit outline"]},{"name":"mdi:pencil-box-outline","tags":["drawing / art","edit box outline"]},{"name":"mdi:pencil-circle","tags":["drawing / art","edit circle"]},{"name":"mdi:pencil-circle-outline","tags":["drawing / art","edit circle outline"]},{"name":"mdi:pencil-lock","tags":["lock"]},{"name":"mdi:pencil-lock-outline","tags":["lock"]},{"name":"mdi:pencil-minus","tags":[]},{"name":"mdi:pencil-minus-outline","tags":[]},{"name":"mdi:pencil-off","tags":["edit off"]},{"name":"mdi:pencil-off-outline","tags":["edit off outline"]},{"name":"mdi:pencil-plus","tags":["pencil add"]},{"name":"mdi:pencil-plus-outline","tags":["pencil add outline"]},{"name":"mdi:pencil-remove","tags":[]},{"name":"mdi:pencil-remove-outline","tags":[]},{"name":"mdi:pencil-ruler","tags":["drawing / art","design"]},{"name":"mdi:pencil-ruler-outline","tags":["drawing / art"]},{"name":"mdi:penguin","tags":["animal","emoji penguin","emoticon penguin","linux"]},{"name":"mdi:pentagon","tags":["shape"]},{"name":"mdi:pentagon-outline","tags":["shape"]},{"name":"mdi:pentagram","tags":[]},{"name":"mdi:percent","tags":["math","shopping","discount","sale"]},{"name":"mdi:percent-box","tags":["math","shopping","discount box","sale box"]},{"name":"mdi:percent-box-outline","tags":["math","shopping","discount box outline","sale box outline"]},{"name":"mdi:percent-circle","tags":["math","shopping","discount circle","sale circle"]},{"name":"mdi:percent-circle-outline","tags":["math","shopping","discount circle outline","sale circle outline"]},{"name":"mdi:percent-outline","tags":["math","shopping","discount outline","sale outline"]},{"name":"mdi:periodic-table","tags":["science"]},{"name":"mdi:perspective-less","tags":["math","perspective decrease"]},{"name":"mdi:perspective-more","tags":["math","perspective increase"]},{"name":"mdi:ph","tags":["science","home automation","acid","base","potential of hydrogen","power of hydrogen"]},{"name":"mdi:phone-alert","tags":["cellphone / phone","alert / error"]},{"name":"mdi:phone-alert-outline","tags":["cellphone / phone","alert / error"]},{"name":"mdi:phone-cancel","tags":["cellphone / phone","phone block"]},{"name":"mdi:phone-cancel-outline","tags":["cellphone / phone"]},{"name":"mdi:phone-check","tags":["cellphone / phone"]},{"name":"mdi:phone-check-outline","tags":["cellphone / phone"]},{"name":"mdi:phone-classic","tags":["cellphone / phone"]},{"name":"mdi:phone-classic-off","tags":[]},{"name":"mdi:phone-clock","tags":["cellphone / phone","date / time","phone schedule","phone time"]},{"name":"mdi:phone-dial","tags":["cellphone / phone","phone keypad"]},{"name":"mdi:phone-dial-outline","tags":["cellphone / phone","phone keypad outline"]},{"name":"mdi:phone-incoming","tags":["cellphone / phone","telephone incoming"]},{"name":"mdi:phone-incoming-outgoing","tags":["cellphone / phone"]},{"name":"mdi:phone-incoming-outgoing-outline","tags":["cellphone / phone"]},{"name":"mdi:phone-log","tags":["cellphone / phone"]},{"name":"mdi:phone-log-outline","tags":["cellphone / phone"]},{"name":"mdi:phone-off","tags":["cellphone / phone"]},{"name":"mdi:phone-outgoing","tags":["cellphone / phone"]},{"name":"mdi:phone-outgoing-outline","tags":["cellphone / phone"]},{"name":"mdi:phone-refresh","tags":["cellphone / phone","phone redial"]},{"name":"mdi:phone-refresh-outline","tags":["cellphone / phone","phone redial outline"]},{"name":"mdi:phone-remove","tags":["cellphone / phone"]},{"name":"mdi:phone-remove-outline","tags":["cellphone / phone"]},{"name":"mdi:phone-return","tags":["cellphone / phone"]},{"name":"mdi:phone-return-outline","tags":["cellphone / phone"]},{"name":"mdi:phone-rotate-landscape","tags":["cellphone / phone"]},{"name":"mdi:phone-rotate-portrait","tags":["cellphone / phone"]},{"name":"mdi:phone-sync","tags":["cellphone / phone","phone redial"]},{"name":"mdi:phone-sync-outline","tags":["cellphone / phone","phone redial outline"]},{"name":"mdi:phone-voip","tags":["cellphone / phone"]},{"name":"mdi:pi","tags":["math"]},{"name":"mdi:pi-box","tags":["math"]},{"name":"mdi:pickaxe","tags":[]},{"name":"mdi:pier","tags":["places","transportation + water"]},{"name":"mdi:pier-crane","tags":["transportation + water","places"]},{"name":"mdi:pig","tags":["animal","agriculture","emoji pig","emoticon pig"]},{"name":"mdi:pill","tags":["medical / hospital","medicine","capsule","drug","pharmaceutical"]},{"name":"mdi:pill-multiple","tags":["medical / hospital","medicine","medication","drugs"]},{"name":"mdi:pill-off","tags":["medical / hospital","medicine off","capsule off","drug off","pharmaceutical off"]},{"name":"mdi:pillar","tags":["historic","column"]},{"name":"mdi:pin-off","tags":["keep off"]},{"name":"mdi:pin-off-outline","tags":["keep off outline"]},{"name":"mdi:pine-tree","tags":["holiday","nature","places","agriculture","forest","plant"]},{"name":"mdi:pine-tree-box","tags":["holiday","nature","agriculture","plant"]},{"name":"mdi:pine-tree-fire","tags":["nature","agriculture","wildfire","controlled burn"]},{"name":"mdi:pine-tree-variant","tags":["nature","places","agriculture"]},{"name":"mdi:pine-tree-variant-outline","tags":["places","nature","agriculture"]},{"name":"mdi:pipe","tags":["home automation"]},{"name":"mdi:pipe-disconnected","tags":["home automation"]},{"name":"mdi:pipe-leak","tags":["home automation"]},{"name":"mdi:pipe-valve","tags":["home automation"]},{"name":"mdi:pirate","tags":[]},{"name":"mdi:pistol","tags":["gun"]},{"name":"mdi:piston","tags":["automotive"]},{"name":"mdi:pitchfork","tags":["hardware / tools"]},{"name":"mdi:plane-car","tags":["transportation + flying","transportation + road","airport shuttle","airport taxi","airplane car"]},{"name":"mdi:plane-train","tags":["transportation + flying","transportation + other","airport shuttle","airplane train"]},{"name":"mdi:play-box","tags":[]},{"name":"mdi:play-box-edit-outline","tags":[]},{"name":"mdi:play-box-lock","tags":["video / movie","lock"]},{"name":"mdi:play-box-lock-open","tags":["video / movie","lock"]},{"name":"mdi:play-box-lock-open-outline","tags":["video / movie","lock"]},{"name":"mdi:play-box-lock-outline","tags":["video / movie","lock"]},{"name":"mdi:play-network","tags":["media network"]},{"name":"mdi:play-network-outline","tags":["media network outline"]},{"name":"mdi:play-outline","tags":[]},{"name":"mdi:play-pause","tags":["home automation"]},{"name":"mdi:playlist-edit","tags":["edit / modify"]},{"name":"mdi:playlist-minus","tags":[]},{"name":"mdi:pliers","tags":["hardware / tools"]},{"name":"mdi:plus-box-multiple-outline","tags":[]},{"name":"mdi:plus-circle-multiple","tags":["coins plus"]},{"name":"mdi:plus-lock","tags":["lock","plus secure"]},{"name":"mdi:plus-lock-open","tags":["lock"]},{"name":"mdi:plus-minus","tags":["math"]},{"name":"mdi:plus-minus-box","tags":["math"]},{"name":"mdi:plus-minus-variant","tags":["math"]},{"name":"mdi:plus-network","tags":["add network"]},{"name":"mdi:plus-network-outline","tags":["add network outline"]},{"name":"mdi:plus-outline","tags":[]},{"name":"mdi:plus-thick","tags":["math","add thick","add bold","plus bold"]},{"name":"mdi:podium","tags":["sport"]},{"name":"mdi:podium-bronze","tags":["sport","podium third"]},{"name":"mdi:podium-gold","tags":["sport","podium first"]},{"name":"mdi:podium-silver","tags":["sport","podium second"]},{"name":"mdi:point-of-sale","tags":[]},{"name":"mdi:pokeball","tags":["gaming / rpg"]},{"name":"mdi:poker-chip","tags":["gaming / rpg","casino chip","gambling chip"]},{"name":"mdi:polaroid","tags":[]},{"name":"mdi:police-badge","tags":[]},{"name":"mdi:police-badge-outline","tags":[]},{"name":"mdi:police-station","tags":["places"]},{"name":"mdi:poll","tags":["bar chart","report","performance","analytics"]},{"name":"mdi:pool","tags":["places","home automation","swimming pool"]},{"name":"mdi:pool-thermometer","tags":["home automation","pool temperature"]},{"name":"mdi:popcorn","tags":["food / drink"]},{"name":"mdi:post-lamp","tags":["home automation","post light"]},{"name":"mdi:post-outline","tags":["blog outline"]},{"name":"mdi:postage-stamp","tags":[]},{"name":"mdi:pot","tags":["food / drink","holiday"]},{"name":"mdi:pot-mix","tags":["food / drink","holiday"]},{"name":"mdi:pot-mix-outline","tags":["food / drink","holiday"]},{"name":"mdi:pot-outline","tags":["food / drink","holiday"]},{"name":"mdi:pot-steam","tags":["food / drink","holiday"]},{"name":"mdi:pot-steam-outline","tags":["food / drink","holiday"]},{"name":"mdi:pound","tags":["hashtag"]},{"name":"mdi:pound-box","tags":["hashtag box"]},{"name":"mdi:pound-box-outline","tags":["hashtag box outline"]},{"name":"mdi:power-cycle","tags":[]},{"name":"mdi:power-off","tags":[]},{"name":"mdi:power-on","tags":[]},{"name":"mdi:power-plug-battery","tags":["home automation","battery","battery backup"]},{"name":"mdi:power-plug-battery-outline","tags":["home automation","battery","battery backup outline"]},{"name":"mdi:power-plug-off","tags":["home automation","power off"]},{"name":"mdi:power-plug-off-outline","tags":["home automation"]},{"name":"mdi:power-plug-outline","tags":["home automation"]},{"name":"mdi:power-sleep","tags":[]},{"name":"mdi:power-socket","tags":["home automation","plug socket"]},{"name":"mdi:power-socket-au","tags":["home automation","plug socket au","power socket type i","power socket cn","power socket ar","power socket nz","power socket pg","power socket australia","power socket china","power socket argentina","power socket new zealand","power socket papua new guinea"]},{"name":"mdi:power-socket-ch","tags":["home automation","plug socket ch","power socket type j","plug socket type j","power socket switzerland","plug socket switzerland"]},{"name":"mdi:power-socket-de","tags":["home automation"]},{"name":"mdi:power-socket-eu","tags":["home automation","plug socket eu","power socket europe"]},{"name":"mdi:power-socket-fr","tags":["home automation"]},{"name":"mdi:power-socket-it","tags":[]},{"name":"mdi:power-socket-jp","tags":["home automation"]},{"name":"mdi:power-socket-uk","tags":["home automation","plug socket uk","power socket type g","power socket ie","power socket hk","power socket my","power socket cy","power socket mt","power socket sg","power socket united kingdom","power socket ireland","power socket hong kong","power socket malaysia","power socket cyprus","power socket malta","power socket singapore"]},{"name":"mdi:power-socket-us","tags":["home automation","plug socket us","power socket ca","power socket mx","power socket type b","power socket united states","power socket japan","power socket canada","power socket mexico"]},{"name":"mdi:power-standby","tags":[]},{"name":"mdi:powershell","tags":[]},{"name":"mdi:prescription","tags":["medical / hospital"]},{"name":"mdi:presentation","tags":[]},{"name":"mdi:presentation-play","tags":[]},{"name":"mdi:pretzel","tags":["food / drink"]},{"name":"mdi:printer-3d","tags":["printer","home automation"]},{"name":"mdi:printer-3d-nozzle","tags":["printer"]},{"name":"mdi:printer-3d-nozzle-alert","tags":["alert / error","printer"]},{"name":"mdi:printer-3d-nozzle-alert-outline","tags":["alert / error","printer"]},{"name":"mdi:printer-3d-nozzle-heat","tags":["printer"]},{"name":"mdi:printer-3d-nozzle-heat-outline","tags":["printer"]},{"name":"mdi:printer-3d-nozzle-off","tags":["printer"]},{"name":"mdi:printer-3d-nozzle-off-outline","tags":["printer"]},{"name":"mdi:printer-3d-nozzle-outline","tags":["printer"]},{"name":"mdi:printer-3d-off","tags":["printer"]},{"name":"mdi:printer-alert","tags":["printer","home automation","alert / error","printer warning","paper jam"]},{"name":"mdi:printer-check","tags":["printer"]},{"name":"mdi:printer-eye","tags":["printer","printer preview","printer view"]},{"name":"mdi:printer-off","tags":["printer"]},{"name":"mdi:printer-pos","tags":["printer","printer point of sale","printer receipt"]},{"name":"mdi:printer-pos-alert","tags":["alert / error","printer","printer point of sale alert","printer receipt alert"]},{"name":"mdi:printer-pos-alert-outline","tags":["printer","alert / error","printer point of sale alert outline","printer receipt alert outline"]},{"name":"mdi:printer-pos-cancel","tags":["printer","printer point of sale cancel","printer receipt cancel"]},{"name":"mdi:printer-pos-cancel-outline","tags":["printer","printer point of sale cancel outline","printer receipt cancel outline"]},{"name":"mdi:printer-pos-check","tags":["printer","printer point of sale check","printer receipt check"]},{"name":"mdi:printer-pos-check-outline","tags":["printer","printer point of sale check outline","printer receipt check outline"]},{"name":"mdi:printer-pos-cog","tags":["printer","printer point of sale cog","printer receipt cog"]},{"name":"mdi:printer-pos-cog-outline","tags":["printer","printer point of sale cog outline","printer receipt cog outline"]},{"name":"mdi:printer-pos-edit","tags":["printer","printer point of sale edit","printer receipt edit"]},{"name":"mdi:printer-pos-edit-outline","tags":["printer","printer point of sale edit outline","printer receipt edit outline"]},{"name":"mdi:printer-pos-minus","tags":["printer","printer point of sale minus","printer receipt minus"]},{"name":"mdi:printer-pos-minus-outline","tags":["printer","printer point of sale minus outline","printer receipt minus outline"]},{"name":"mdi:printer-pos-network","tags":["printer","printer point of sale network","printer receipt network"]},{"name":"mdi:printer-pos-network-outline","tags":["printer","printer point of sale network outline","printer receipt network outline"]},{"name":"mdi:printer-pos-off","tags":["printer","printer point of sale off","printer receipt off"]},{"name":"mdi:printer-pos-off-outline","tags":["printer","printer point of sale off outline","printer receipt off outline"]},{"name":"mdi:printer-pos-outline","tags":["printer","printer point of sale outline","printer receipt outline"]},{"name":"mdi:printer-pos-pause","tags":["printer","printer point of sale pause","printer receipt pause"]},{"name":"mdi:printer-pos-pause-outline","tags":["printer","printer point of sale pause outline","printer receipt pause outline"]},{"name":"mdi:printer-pos-play","tags":["printer","printer point of sale play","printer receipt play"]},{"name":"mdi:printer-pos-play-outline","tags":["printer","printer point of sale play outline","printer receipt play outline"]},{"name":"mdi:printer-pos-plus","tags":["printer","printer point of sale plus","printer receipt plus"]},{"name":"mdi:printer-pos-plus-outline","tags":["printer","printer point of sale plus outline","printer receipt plus outline"]},{"name":"mdi:printer-pos-refresh","tags":["printer","printer point of sale refresh","printer receipt refresh"]},{"name":"mdi:printer-pos-refresh-outline","tags":["printer","printer point of sale refresh outline","printer receipt refresh outline"]},{"name":"mdi:printer-pos-remove","tags":["printer","printer point of sale remove","printer receipt remove"]},{"name":"mdi:printer-pos-remove-outline","tags":["printer","printer point of sale remove outline","printer receipt remove outline"]},{"name":"mdi:printer-pos-star","tags":["printer","printer point of sale star","printer receipt star","printer favorite","printer primary"]},{"name":"mdi:printer-pos-star-outline","tags":["printer","printer point of sale star outline","printer receipt star outline"]},{"name":"mdi:printer-pos-stop","tags":["printer","printer point of sale stop","printer receipt stop"]},{"name":"mdi:printer-pos-stop-outline","tags":["printer","printer point of sale stop outline","printer receipt stop outline"]},{"name":"mdi:printer-pos-sync","tags":["printer","printer point of sale sync","printer receipt sync"]},{"name":"mdi:printer-pos-sync-outline","tags":["printer","printer point of sale sync outline","printer receipt sync outline"]},{"name":"mdi:printer-pos-wrench","tags":["printer","printer point of sale wrench","printer receipt wrench"]},{"name":"mdi:printer-pos-wrench-outline","tags":["printer","printer point of sale wrench outline","printer receipt wrench outline"]},{"name":"mdi:printer-search","tags":["printer","printer preview","printer magnify"]},{"name":"mdi:printer-settings","tags":["settings","printer"]},{"name":"mdi:printer-wireless","tags":["printer"]},{"name":"mdi:professional-hexagon","tags":[]},{"name":"mdi:progress-alert","tags":["alert / error","progress warning"]},{"name":"mdi:progress-check","tags":["progress tick"]},{"name":"mdi:progress-clock","tags":["date / time"]},{"name":"mdi:progress-close","tags":[]},{"name":"mdi:progress-download","tags":[]},{"name":"mdi:progress-helper","tags":[]},{"name":"mdi:progress-pencil","tags":[]},{"name":"mdi:progress-question","tags":[]},{"name":"mdi:progress-star","tags":[]},{"name":"mdi:progress-star-four-points","tags":["progress auto"]},{"name":"mdi:progress-upload","tags":[]},{"name":"mdi:progress-wrench","tags":["hardware / tools","progress spanner"]},{"name":"mdi:projector","tags":["device / tech","home automation"]},{"name":"mdi:projector-off","tags":["device / tech","home automation"]},{"name":"mdi:projector-screen","tags":["device / tech","home automation"]},{"name":"mdi:projector-screen-off","tags":["home automation"]},{"name":"mdi:projector-screen-off-outline","tags":["home automation"]},{"name":"mdi:projector-screen-outline","tags":["home automation"]},{"name":"mdi:projector-screen-variant","tags":["home automation"]},{"name":"mdi:projector-screen-variant-off","tags":["home automation"]},{"name":"mdi:projector-screen-variant-off-outline","tags":["home automation"]},{"name":"mdi:projector-screen-variant-outline","tags":["home automation"]},{"name":"mdi:protocol","tags":[]},{"name":"mdi:publish-off","tags":["arrow","publish disabled"]},{"name":"mdi:pulse","tags":["medical / hospital","vitals"]},{"name":"mdi:pump","tags":[]},{"name":"mdi:pump-off","tags":[]},{"name":"mdi:pumpkin","tags":["holiday"]},{"name":"mdi:purse","tags":[]},{"name":"mdi:purse-outline","tags":[]},{"name":"mdi:puzzle-check","tags":["gaming / rpg"]},{"name":"mdi:puzzle-check-outline","tags":["gaming / rpg"]},{"name":"mdi:puzzle-edit","tags":["gaming / rpg","edit / modify"]},{"name":"mdi:puzzle-edit-outline","tags":["gaming / rpg","edit / modify"]},{"name":"mdi:puzzle-heart","tags":["gaming / rpg"]},{"name":"mdi:puzzle-heart-outline","tags":["gaming / rpg"]},{"name":"mdi:puzzle-minus","tags":["gaming / rpg"]},{"name":"mdi:puzzle-minus-outline","tags":["gaming / rpg"]},{"name":"mdi:puzzle-plus","tags":["gaming / rpg"]},{"name":"mdi:puzzle-plus-outline","tags":["gaming / rpg"]},{"name":"mdi:puzzle-remove","tags":["gaming / rpg"]},{"name":"mdi:puzzle-remove-outline","tags":["gaming / rpg"]},{"name":"mdi:puzzle-star","tags":["gaming / rpg","puzzle favorite"]},{"name":"mdi:puzzle-star-outline","tags":["gaming / rpg","puzzle favorite outline"]},{"name":"mdi:pyramid","tags":["shape"]},{"name":"mdi:pyramid-off","tags":["shape"]},{"name":"mdi:qrcode","tags":[]},{"name":"mdi:qrcode-edit","tags":["edit / modify"]},{"name":"mdi:qrcode-minus","tags":[]},{"name":"mdi:qrcode-plus","tags":[]},{"name":"mdi:qrcode-remove","tags":[]},{"name":"mdi:qrcode-scan","tags":[]},{"name":"mdi:quadcopter","tags":["drone"]},{"name":"mdi:quality-low","tags":["low quality","lq"]},{"name":"mdi:quality-medium","tags":["medium quality","mq"]},{"name":"mdi:quora","tags":[]},{"name":"mdi:rabbit","tags":["animal","nature","bunny","hare"]},{"name":"mdi:radiator","tags":["home automation","heater"]},{"name":"mdi:radiator-disabled","tags":["home automation","heater disabled"]},{"name":"mdi:radiator-off","tags":["home automation","heater off"]},{"name":"mdi:radio-am","tags":["audio"]},{"name":"mdi:radio-fm","tags":["audio"]},{"name":"mdi:radio-handheld","tags":["device / tech"]},{"name":"mdi:radio-off","tags":[]},{"name":"mdi:radio-tower","tags":[]},{"name":"mdi:radioactive","tags":["science","radiation"]},{"name":"mdi:radioactive-circle","tags":["science","radiation circle"]},{"name":"mdi:radioactive-circle-outline","tags":["science","radiation circle outline"]},{"name":"mdi:radioactive-off","tags":["science","radiation off"]},{"name":"mdi:radiobox-indeterminate-variant","tags":["form","radio button indeterminate","radiobox intermediate variant"]},{"name":"mdi:radiology-box","tags":["medical / hospital","x ray box"]},{"name":"mdi:radiology-box-outline","tags":["medical / hospital","x ray box outline"]},{"name":"mdi:radius","tags":["math","circle radius","sphere radius"]},{"name":"mdi:radius-outline","tags":["math","circle radius outline","sphere radius outline"]},{"name":"mdi:railroad-light","tags":["transportation + other","railroad crossing light","train crossing light","level crossing signals"]},{"name":"mdi:rake","tags":["hardware / tools"]},{"name":"mdi:raspberry-pi","tags":["raspberrypi"]},{"name":"mdi:ray-end","tags":[]},{"name":"mdi:ray-end-arrow","tags":[]},{"name":"mdi:ray-start","tags":[]},{"name":"mdi:ray-start-arrow","tags":[]},{"name":"mdi:ray-start-end","tags":[]},{"name":"mdi:ray-vertex","tags":[]},{"name":"mdi:razor-double-edge","tags":["health / beauty","hardware / tools"]},{"name":"mdi:razor-single-edge","tags":["hardware / tools"]},{"name":"mdi:read","tags":[]},{"name":"mdi:receipt","tags":["cloth","fabric","swatch"]},{"name":"mdi:receipt-clock","tags":["receipt pending"]},{"name":"mdi:receipt-clock-outline","tags":["receipt pending"]},{"name":"mdi:receipt-outline","tags":["cloth outline","fabric outline","swatch outline"]},{"name":"mdi:receipt-send","tags":[]},{"name":"mdi:receipt-send-outline","tags":[]},{"name":"mdi:receipt-text-arrow-left","tags":["invoice arrow left","invoice receive"]},{"name":"mdi:receipt-text-arrow-left-outline","tags":["invoice arrow left outline","invoice receive outline"]},{"name":"mdi:receipt-text-arrow-right","tags":["invoice arrow right","invoice send"]},{"name":"mdi:receipt-text-arrow-right-outline","tags":["invoice arrow right outline","invoice send outline"]},{"name":"mdi:receipt-text-check","tags":["invoice check"]},{"name":"mdi:receipt-text-check-outline","tags":["invoice check outline"]},{"name":"mdi:receipt-text-clock","tags":["invoice clock","invoice schedule","receipt text pending"]},{"name":"mdi:receipt-text-clock-outline","tags":["invoice clock outline","invoice schedule outline","receipt text pending"]},{"name":"mdi:receipt-text-edit","tags":["invoice edit"]},{"name":"mdi:receipt-text-edit-outline","tags":["invoice edit outline"]},{"name":"mdi:receipt-text-minus","tags":["invoice minus"]},{"name":"mdi:receipt-text-minus-outline","tags":["invoice minus outline"]},{"name":"mdi:receipt-text-plus","tags":["invoice plus","invoice add","receipt text add"]},{"name":"mdi:receipt-text-plus-outline","tags":["invoice plus","invoice add","receipt text add"]},{"name":"mdi:receipt-text-remove","tags":["invoice remove"]},{"name":"mdi:receipt-text-remove-outline","tags":["invoice remove outline"]},{"name":"mdi:receipt-text-send","tags":[]},{"name":"mdi:receipt-text-send-outline","tags":[]},{"name":"mdi:record","tags":["home automation","fiber manual record"]},{"name":"mdi:record-circle","tags":[]},{"name":"mdi:record-circle-outline","tags":[]},{"name":"mdi:record-player","tags":["home automation"]},{"name":"mdi:record-rec","tags":["home automation"]},{"name":"mdi:rectangle","tags":["shape"]},{"name":"mdi:rectangle-outline","tags":["shape"]},{"name":"mdi:recycle","tags":[]},{"name":"mdi:recycle-variant","tags":[]},{"name":"mdi:redo-variant","tags":["arrow"]},{"name":"mdi:reflect-horizontal","tags":[]},{"name":"mdi:reflect-vertical","tags":[]},{"name":"mdi:refresh-auto","tags":["automotive","auto start","automatic start","auto stop","automatic stop","automatic","refresh automatic"]},{"name":"mdi:refresh-circle","tags":[]},{"name":"mdi:regex","tags":["regular expression"]},{"name":"mdi:registered-trademark","tags":[]},{"name":"mdi:reiterate","tags":["arrow"]},{"name":"mdi:relation-many-to-many","tags":["database"]},{"name":"mdi:relation-many-to-one","tags":["database"]},{"name":"mdi:relation-many-to-one-or-many","tags":["database"]},{"name":"mdi:relation-many-to-only-one","tags":["database"]},{"name":"mdi:relation-many-to-zero-or-many","tags":["database"]},{"name":"mdi:relation-many-to-zero-or-one","tags":["database"]},{"name":"mdi:relation-one-or-many-to-many","tags":["database"]},{"name":"mdi:relation-one-or-many-to-one","tags":["database"]},{"name":"mdi:relation-one-or-many-to-one-or-many","tags":["database"]},{"name":"mdi:relation-one-or-many-to-only-one","tags":["database"]},{"name":"mdi:relation-one-or-many-to-zero-or-many","tags":["database"]},{"name":"mdi:relation-one-or-many-to-zero-or-one","tags":["database"]},{"name":"mdi:relation-one-to-many","tags":["database"]},{"name":"mdi:relation-one-to-one","tags":["database"]},{"name":"mdi:relation-one-to-one-or-many","tags":["database"]},{"name":"mdi:relation-one-to-only-one","tags":["database"]},{"name":"mdi:relation-one-to-zero-or-many","tags":["database"]},{"name":"mdi:relation-one-to-zero-or-one","tags":["database"]},{"name":"mdi:relation-only-one-to-many","tags":["database"]},{"name":"mdi:relation-only-one-to-one","tags":["database"]},{"name":"mdi:relation-only-one-to-one-or-many","tags":["database"]},{"name":"mdi:relation-only-one-to-only-one","tags":["database"]},{"name":"mdi:relation-only-one-to-zero-or-many","tags":["database"]},{"name":"mdi:relation-only-one-to-zero-or-one","tags":["database"]},{"name":"mdi:relation-zero-or-many-to-many","tags":["database"]},{"name":"mdi:relation-zero-or-many-to-one","tags":["database"]},{"name":"mdi:relation-zero-or-many-to-one-or-many","tags":["database"]},{"name":"mdi:relation-zero-or-many-to-only-one","tags":["database"]},{"name":"mdi:relation-zero-or-many-to-zero-or-many","tags":["database"]},{"name":"mdi:relation-zero-or-many-to-zero-or-one","tags":["database"]},{"name":"mdi:relation-zero-or-one-to-many","tags":["database"]},{"name":"mdi:relation-zero-or-one-to-one","tags":["database"]},{"name":"mdi:relation-zero-or-one-to-one-or-many","tags":["database"]},{"name":"mdi:relation-zero-or-one-to-only-one","tags":["database"]},{"name":"mdi:relation-zero-or-one-to-zero-or-many","tags":["database"]},{"name":"mdi:relation-zero-or-one-to-zero-or-one","tags":["database"]},{"name":"mdi:reload","tags":["automotive","arrow","car engine start","loop","rotate clockwise"]},{"name":"mdi:reload-alert","tags":["alert / error"]},{"name":"mdi:remote-desktop","tags":[]},{"name":"mdi:remote-off","tags":[]},{"name":"mdi:remote-tv","tags":["device / tech"]},{"name":"mdi:remote-tv-off","tags":["device / tech"]},{"name":"mdi:rename-box","tags":[]},{"name":"mdi:rename-box-outline","tags":[]},{"name":"mdi:reorder-vertical","tags":[]},{"name":"mdi:repeat-off","tags":[]},{"name":"mdi:repeat-variant","tags":["arrow","twitter retweet","repost"]},{"name":"mdi:reply-all-outline","tags":["arrow"]},{"name":"mdi:reply-circle","tags":["arrow"]},{"name":"mdi:reply-outline","tags":["arrow"]},{"name":"mdi:reproduction","tags":["medical / hospital"]},{"name":"mdi:resistor","tags":[]},{"name":"mdi:resistor-nodes","tags":[]},{"name":"mdi:resize","tags":[]},{"name":"mdi:resize-bottom-right","tags":["drag"]},{"name":"mdi:responsive","tags":[]},{"name":"mdi:restart-alert","tags":["alert / error"]},{"name":"mdi:restart-off","tags":[]},{"name":"mdi:restore-alert","tags":["alert / error"]},{"name":"mdi:rewind-10","tags":[]},{"name":"mdi:rewind-15","tags":[]},{"name":"mdi:rewind-30","tags":[]},{"name":"mdi:rewind-45","tags":[]},{"name":"mdi:rewind-5","tags":[]},{"name":"mdi:rewind-60","tags":[]},{"name":"mdi:rhombus","tags":["shape","diamond"]},{"name":"mdi:rhombus-medium","tags":["shape"]},{"name":"mdi:rhombus-medium-outline","tags":["shape"]},{"name":"mdi:rhombus-outline","tags":["shape","diamond outline"]},{"name":"mdi:rhombus-split","tags":["shape","collection"]},{"name":"mdi:rhombus-split-outline","tags":["shape"]},{"name":"mdi:rice","tags":["food / drink"]},{"name":"mdi:rickshaw","tags":["transportation + road","transportation + other"]},{"name":"mdi:rickshaw-electric","tags":["transportation + road","transportation + other"]},{"name":"mdi:ring","tags":[]},{"name":"mdi:rivet","tags":["hardware / tools"]},{"name":"mdi:road","tags":["transportation + road"]},{"name":"mdi:road-variant","tags":["transportation + road"]},{"name":"mdi:robber","tags":[]},{"name":"mdi:robot","tags":["home automation","emoji robot","emoticon robot"]},{"name":"mdi:robot-angry","tags":["emoji robot angry","emoticon robot angry"]},{"name":"mdi:robot-angry-outline","tags":["emoji robot angry outline","emoticon robot angry outline"]},{"name":"mdi:robot-confused","tags":["emoji robot confused","emoticon robot confused"]},{"name":"mdi:robot-confused-outline","tags":["emoji robot confused outline","emoticon robot confused outline"]},{"name":"mdi:robot-dead","tags":["emoji robot dead","emoticon robot dead"]},{"name":"mdi:robot-dead-outline","tags":["emoji robot dead outline","emoticon robot dead outline"]},{"name":"mdi:robot-excited","tags":["emoticon robot excited","emoji robot excited"]},{"name":"mdi:robot-excited-outline","tags":["emoji robot excited outline","emoticon robot excited outline"]},{"name":"mdi:robot-happy","tags":["emoji robot happy","emoticon robot happy"]},{"name":"mdi:robot-happy-outline","tags":["emoji robot happy outline","emoticon robot happy outline"]},{"name":"mdi:robot-industrial","tags":["autonomous","assembly"]},{"name":"mdi:robot-industrial-outline","tags":[]},{"name":"mdi:robot-love","tags":["emoji robot love","emoticon robot love"]},{"name":"mdi:robot-love-outline","tags":[]},{"name":"mdi:robot-mower","tags":["home automation","lawn mower"]},{"name":"mdi:robot-mower-outline","tags":["home automation","lawn mower outline"]},{"name":"mdi:robot-off","tags":["emoji robot off","emoticon robot off"]},{"name":"mdi:robot-off-outline","tags":[]},{"name":"mdi:robot-outline","tags":["emoji robot outline","emoticon robot outline"]},{"name":"mdi:robot-vacuum","tags":["device / tech","home automation","roomba"]},{"name":"mdi:robot-vacuum-alert","tags":["alert / error","home automation","robot vacuum error"]},{"name":"mdi:robot-vacuum-off","tags":["home automation"]},{"name":"mdi:robot-vacuum-variant","tags":["home automation","neato"]},{"name":"mdi:robot-vacuum-variant-alert","tags":["alert / error","home automation","robot vacuum variant error"]},{"name":"mdi:robot-vacuum-variant-off","tags":["home automation"]},{"name":"mdi:rocket-launch","tags":["science","transportation + flying"]},{"name":"mdi:rocket-launch-outline","tags":["science","transportation + flying"]},{"name":"mdi:roller-skate","tags":["sport"]},{"name":"mdi:roller-skate-off","tags":["sport"]},{"name":"mdi:rollerblade","tags":["sport"]},{"name":"mdi:rollerblade-off","tags":["sport"]},{"name":"mdi:rolodex","tags":[]},{"name":"mdi:rolodex-outline","tags":[]},{"name":"mdi:roman-numeral-1","tags":["alpha / numeric"]},{"name":"mdi:roman-numeral-10","tags":["alpha / numeric"]},{"name":"mdi:roman-numeral-2","tags":["alpha / numeric"]},{"name":"mdi:roman-numeral-3","tags":["alpha / numeric"]},{"name":"mdi:roman-numeral-4","tags":["alpha / numeric"]},{"name":"mdi:roman-numeral-5","tags":["alpha / numeric"]},{"name":"mdi:roman-numeral-6","tags":["alpha / numeric"]},{"name":"mdi:roman-numeral-7","tags":["alpha / numeric"]},{"name":"mdi:roman-numeral-8","tags":["alpha / numeric"]},{"name":"mdi:roman-numeral-9","tags":["alpha / numeric"]},{"name":"mdi:rotate-3d-variant","tags":["3d rotation"]},{"name":"mdi:rotate-left-variant","tags":[]},{"name":"mdi:rotate-orbit","tags":["gyro","accelerometer"]},{"name":"mdi:rotate-right-variant","tags":[]},{"name":"mdi:router","tags":[]},{"name":"mdi:router-network","tags":[]},{"name":"mdi:router-wireless-off","tags":[]},{"name":"mdi:routes","tags":["sign routes"]},{"name":"mdi:routes-clock","tags":["date / time"]},{"name":"mdi:rss-box","tags":["rss feed box"]},{"name":"mdi:rss-off","tags":[]},{"name":"mdi:rug","tags":["home automation","carpet"]},{"name":"mdi:ruler","tags":["hardware / tools","drawing / art"]},{"name":"mdi:ruler-square","tags":["hardware / tools","drawing / art","square","carpentry","architecture"]},{"name":"mdi:ruler-square-compass","tags":["hardware / tools","mason","masonic","freemasonry"]},{"name":"mdi:run-fast","tags":["home automation","sport","people / family","velocity","human run fast"]},{"name":"mdi:rv-truck","tags":["transportation + road","recreational vehicle","campervan"]},{"name":"mdi:sack","tags":["gaming / rpg"]},{"name":"mdi:sack-outline","tags":[]},{"name":"mdi:sack-percent","tags":[]},{"name":"mdi:safe","tags":["banking"]},{"name":"mdi:safe-square","tags":[]},{"name":"mdi:safe-square-outline","tags":[]},{"name":"mdi:safety-goggles","tags":["science","safety glasses"]},{"name":"mdi:sail-boat-sink","tags":["transportation + water","sail boat crash","sail boat wreck"]},{"name":"mdi:sale","tags":["shopping","discount"]},{"name":"mdi:sale-outline","tags":["shopping","discount outline"]},{"name":"mdi:satellite-uplink","tags":[]},{"name":"mdi:satellite-variant","tags":[]},{"name":"mdi:sausage","tags":["food / drink"]},{"name":"mdi:sausage-off","tags":["food / drink"]},{"name":"mdi:saw-blade","tags":["hardware / tools"]},{"name":"mdi:sawtooth-wave","tags":["audio"]},{"name":"mdi:scale","tags":["food / drink","science"]},{"name":"mdi:scale-balance","tags":["science","justice","legal"]},{"name":"mdi:scale-bathroom","tags":["home automation","medical / hospital"]},{"name":"mdi:scale-off","tags":["science"]},{"name":"mdi:scale-unbalanced","tags":[]},{"name":"mdi:scan-helper","tags":[]},{"name":"mdi:scanner-off","tags":["device / tech"]},{"name":"mdi:scent","tags":["aroma","fragrance","smell","odor"]},{"name":"mdi:scent-off","tags":["aroma off","smell off","fragrance off","odor off"]},{"name":"mdi:scissors-cutting","tags":[]},{"name":"mdi:scoreboard","tags":["sport"]},{"name":"mdi:scoreboard-outline","tags":["sport"]},{"name":"mdi:screw-flat-top","tags":["hardware / tools"]},{"name":"mdi:screw-lag","tags":["hardware / tools"]},{"name":"mdi:screw-machine-flat-top","tags":["hardware / tools"]},{"name":"mdi:screw-machine-round-top","tags":["hardware / tools"]},{"name":"mdi:screw-round-top","tags":["hardware / tools"]},{"name":"mdi:screwdriver","tags":["hardware / tools"]},{"name":"mdi:script","tags":["gaming / rpg","scroll"]},{"name":"mdi:script-outline","tags":["gaming / rpg","scroll outline"]},{"name":"mdi:script-text","tags":["gaming / rpg","scroll text"]},{"name":"mdi:script-text-key","tags":[]},{"name":"mdi:script-text-key-outline","tags":[]},{"name":"mdi:script-text-outline","tags":["gaming / rpg","scroll text outline"]},{"name":"mdi:script-text-play","tags":[]},{"name":"mdi:script-text-play-outline","tags":[]},{"name":"mdi:seal","tags":["ribbon","prize","award"]},{"name":"mdi:seal-variant","tags":["ribbon","prize","award"]},{"name":"mdi:search-web","tags":["search globe","global search","internet search"]},{"name":"mdi:seat-passenger","tags":[]},{"name":"mdi:seatbelt","tags":["automotive","seat belt","safety belt"]},{"name":"mdi:security-network","tags":["shield network","uac network","administrator network"]},{"name":"mdi:seed","tags":["agriculture","nature","food / drink"]},{"name":"mdi:seed-off","tags":["nature","food / drink","agriculture"]},{"name":"mdi:seed-off-outline","tags":["nature","food / drink","agriculture"]},{"name":"mdi:seed-outline","tags":["agriculture","nature","food / drink"]},{"name":"mdi:seed-plus","tags":["agriculture","nature","seed add"]},{"name":"mdi:seed-plus-outline","tags":["agriculture","nature","seed add outline"]},{"name":"mdi:seesaw","tags":["playground seesaw"]},{"name":"mdi:select","tags":[]},{"name":"mdi:select-arrow-down","tags":[]},{"name":"mdi:select-arrow-up","tags":[]},{"name":"mdi:select-compare","tags":[]},{"name":"mdi:select-drag","tags":[]},{"name":"mdi:select-inverse","tags":["selection invert"]},{"name":"mdi:select-marker","tags":["navigation","select location"]},{"name":"mdi:select-multiple","tags":[]},{"name":"mdi:select-multiple-marker","tags":["navigation","select multiple location"]},{"name":"mdi:select-off","tags":[]},{"name":"mdi:select-place","tags":[]},{"name":"mdi:select-remove","tags":[]},{"name":"mdi:select-search","tags":[]},{"name":"mdi:selection","tags":[]},{"name":"mdi:selection-drag","tags":[]},{"name":"mdi:selection-ellipse","tags":[]},{"name":"mdi:selection-ellipse-remove","tags":[]},{"name":"mdi:selection-marker","tags":["navigation","selection location"]},{"name":"mdi:selection-multiple","tags":[]},{"name":"mdi:selection-multiple-marker","tags":["navigation","selection multiple location"]},{"name":"mdi:selection-off","tags":[]},{"name":"mdi:selection-remove","tags":[]},{"name":"mdi:selection-search","tags":[]},{"name":"mdi:send-check","tags":[]},{"name":"mdi:send-check-outline","tags":[]},{"name":"mdi:send-circle","tags":[]},{"name":"mdi:send-circle-outline","tags":[]},{"name":"mdi:send-lock","tags":["lock","send secure"]},{"name":"mdi:send-lock-outline","tags":["lock"]},{"name":"mdi:send-outline","tags":["paper airplane outline","paper plane outline"]},{"name":"mdi:send-variant-clock","tags":[]},{"name":"mdi:send-variant-clock-outline","tags":[]},{"name":"mdi:serial-port","tags":["vga"]},{"name":"mdi:server","tags":["storage"]},{"name":"mdi:server-minus","tags":["server remove"]},{"name":"mdi:server-network","tags":[]},{"name":"mdi:server-network-off","tags":[]},{"name":"mdi:server-off","tags":[]},{"name":"mdi:server-plus","tags":["server add"]},{"name":"mdi:server-remove","tags":[]},{"name":"mdi:server-security","tags":["server shield"]},{"name":"mdi:set-all","tags":["database","set union","set or","full outer join","sql full outer join"]},{"name":"mdi:set-center","tags":["database","set centre","set intersection","set and","inner join","sql inner join"]},{"name":"mdi:set-center-right","tags":["database","set centre right","outer join right","sql right outer join"]},{"name":"mdi:set-left","tags":["database","difference left"]},{"name":"mdi:set-left-center","tags":["database","set left centre","outer join left","sql left outer join"]},{"name":"mdi:set-left-right","tags":["database","exclusion","set xor"]},{"name":"mdi:set-merge","tags":[]},{"name":"mdi:set-none","tags":["database","set null","set not","venn diagram"]},{"name":"mdi:set-right","tags":["database","difference right"]},{"name":"mdi:set-split","tags":[]},{"name":"mdi:set-top-box","tags":["home automation"]},{"name":"mdi:settings-helper","tags":["settings"]},{"name":"mdi:shaker","tags":["food / drink","pepper","fish food"]},{"name":"mdi:shaker-outline","tags":["food / drink","salt","fish food outline"]},{"name":"mdi:shape-circle-plus","tags":["shape","shape circle add"]},{"name":"mdi:shape-oval-plus","tags":[]},{"name":"mdi:shape-plus","tags":["shape","shape add","category plus"]},{"name":"mdi:shape-plus-outline","tags":["shape","shape add outline","category plus outline"]},{"name":"mdi:shape-polygon-plus","tags":["shape","shape polygon add"]},{"name":"mdi:shape-rectangle-plus","tags":["shape","shape rectangle add"]},{"name":"mdi:shape-square-plus","tags":["shape","shape square add"]},{"name":"mdi:shape-square-rounded-plus","tags":[]},{"name":"mdi:share-all","tags":[]},{"name":"mdi:share-all-outline","tags":[]},{"name":"mdi:share-circle","tags":["arrow"]},{"name":"mdi:share-off","tags":["arrow","forward off"]},{"name":"mdi:share-off-outline","tags":["arrow","forward off outline"]},{"name":"mdi:share-outline","tags":["arrow","forward outline"]},{"name":"mdi:share-variant-outline","tags":[]},{"name":"mdi:shark","tags":["animal","jaws"]},{"name":"mdi:shark-fin","tags":["animal"]},{"name":"mdi:shark-fin-outline","tags":["animal"]},{"name":"mdi:shark-off","tags":["animal","jaws off"]},{"name":"mdi:sheep","tags":["animal","agriculture","emoji sheep","emoticon sheep"]},{"name":"mdi:shield","tags":["gaming / rpg"]},{"name":"mdi:shield-account","tags":["account / user","home automation","security account","shield user","shield person","alarm arm home"]},{"name":"mdi:shield-account-outline","tags":["account / user","home automation","security account outline","shield user outline","shield person outline","alarm arm home outline"]},{"name":"mdi:shield-airplane","tags":["transportation + flying","shield aeroplane","shield plane","plane shield"]},{"name":"mdi:shield-airplane-outline","tags":["transportation + flying","shield aeroplane outline","shield plane outline"]},{"name":"mdi:shield-alert","tags":["alert / error","shield warning"]},{"name":"mdi:shield-alert-outline","tags":["alert / error","shield warning outline"]},{"name":"mdi:shield-bug","tags":["antivirus"]},{"name":"mdi:shield-bug-outline","tags":["antivirus outline"]},{"name":"mdi:shield-car","tags":["automotive","car security","car insurance"]},{"name":"mdi:shield-check-outline","tags":["shield tick outline"]},{"name":"mdi:shield-cross","tags":["gaming / rpg","religion","shield templar","shield christianity"]},{"name":"mdi:shield-cross-outline","tags":["gaming / rpg","religion","shield templar outline","shield christianity outline"]},{"name":"mdi:shield-crown","tags":["gaming / rpg","administrator"]},{"name":"mdi:shield-crown-outline","tags":["gaming / rpg","administrator outline"]},{"name":"mdi:shield-edit","tags":["edit / modify"]},{"name":"mdi:shield-edit-outline","tags":["edit / modify"]},{"name":"mdi:shield-half","tags":[]},{"name":"mdi:shield-half-full","tags":[]},{"name":"mdi:shield-home","tags":["home automation","security home","shield house","alarm arm home"]},{"name":"mdi:shield-home-outline","tags":["home automation","shield house outline","alarm arm home"]},{"name":"mdi:shield-link-variant","tags":[]},{"name":"mdi:shield-link-variant-outline","tags":[]},{"name":"mdi:shield-lock","tags":["lock","home automation","security lock","alarm arm away"]},{"name":"mdi:shield-lock-open","tags":["home automation","lock","shield unlocked"]},{"name":"mdi:shield-lock-open-outline","tags":["home automation","lock","shield unlocked outline"]},{"name":"mdi:shield-lock-outline","tags":["lock","home automation","alarm arm away outline","security lock outline"]},{"name":"mdi:shield-moon","tags":["home automation","alarm arm night"]},{"name":"mdi:shield-moon-outline","tags":["home automation","alarm arm night outline"]},{"name":"mdi:shield-off","tags":["security off"]},{"name":"mdi:shield-off-outline","tags":[]},{"name":"mdi:shield-outline","tags":["gaming / rpg"]},{"name":"mdi:shield-refresh","tags":[]},{"name":"mdi:shield-refresh-outline","tags":[]},{"name":"mdi:shield-remove","tags":[]},{"name":"mdi:shield-remove-outline","tags":[]},{"name":"mdi:shield-star","tags":["badge","shield favorite"]},{"name":"mdi:shield-star-outline","tags":["badge outline","shield favorite outline"]},{"name":"mdi:shield-sun","tags":["weather","sun protection"]},{"name":"mdi:shield-sun-outline","tags":["weather","sun protection outline"]},{"name":"mdi:shield-sword","tags":["gaming / rpg","moderator"]},{"name":"mdi:shield-sword-outline","tags":["gaming / rpg","moderator outline"]},{"name":"mdi:shield-sync","tags":[]},{"name":"mdi:shield-sync-outline","tags":[]},{"name":"mdi:shimmer","tags":["sparkles"]},{"name":"mdi:shipping-pallet","tags":[]},{"name":"mdi:shoe-ballet","tags":["sport","clothing","slippers ballet"]},{"name":"mdi:shoe-cleat","tags":["sport","clothing"]},{"name":"mdi:shoe-formal","tags":["clothing"]},{"name":"mdi:shoe-heel","tags":["clothing"]},{"name":"mdi:shoe-print","tags":["footprints"]},{"name":"mdi:shoe-sneaker","tags":["sport","clothing","shoe running"]},{"name":"mdi:shopping-music","tags":["shopping"]},{"name":"mdi:shopping-search","tags":["shopping"]},{"name":"mdi:shopping-search-outline","tags":["shopping"]},{"name":"mdi:shore","tags":[]},{"name":"mdi:shovel","tags":["hardware / tools","gardening"]},{"name":"mdi:shovel-off","tags":["hardware / tools"]},{"name":"mdi:shower","tags":["home automation","bathtub","bathroom"]},{"name":"mdi:shower-head","tags":["home automation","bathroom"]},{"name":"mdi:shredder","tags":[]},{"name":"mdi:shuffle-disabled","tags":["arrow"]},{"name":"mdi:shuffle-variant","tags":["arrow"]},{"name":"mdi:shuriken","tags":["ninja star"]},{"name":"mdi:sickle","tags":["hardware / tools"]},{"name":"mdi:sigma-lower","tags":[]},{"name":"mdi:sign-caution","tags":["transportation + road","barrier"]},{"name":"mdi:sign-direction","tags":["milestone"]},{"name":"mdi:sign-direction-minus","tags":["milestone minus"]},{"name":"mdi:sign-direction-plus","tags":["milestone plus","sign direction add","milestone add"]},{"name":"mdi:sign-direction-remove","tags":["milestone remove"]},{"name":"mdi:sign-pole","tags":[]},{"name":"mdi:sign-real-estate","tags":[]},{"name":"mdi:sign-text","tags":[]},{"name":"mdi:sign-yield","tags":["transportation + road","give way"]},{"name":"mdi:signal","tags":["cellphone / phone"]},{"name":"mdi:signal-2g","tags":["cellphone / phone"]},{"name":"mdi:signal-3g","tags":["cellphone / phone"]},{"name":"mdi:signal-4g","tags":["cellphone / phone"]},{"name":"mdi:signal-5g","tags":["cellphone / phone"]},{"name":"mdi:signal-cellular-1","tags":["cellphone / phone"]},{"name":"mdi:signal-cellular-2","tags":["cellphone / phone"]},{"name":"mdi:signal-cellular-3","tags":["cellphone / phone"]},{"name":"mdi:signal-cellular-outline","tags":["cellphone / phone","signal cellular 0"]},{"name":"mdi:signal-distance-variant","tags":[]},{"name":"mdi:signal-hspa","tags":["cellphone / phone"]},{"name":"mdi:signal-hspa-plus","tags":["cellphone / phone"]},{"name":"mdi:signal-off","tags":["cellphone / phone"]},{"name":"mdi:signal-variant","tags":[]},{"name":"mdi:signature","tags":["form"]},{"name":"mdi:signature-freehand","tags":["form"]},{"name":"mdi:signature-image","tags":["form"]},{"name":"mdi:signature-text","tags":["form"]},{"name":"mdi:silo","tags":["agriculture","farm"]},{"name":"mdi:silo-outline","tags":["agriculture","farm outline"]},{"name":"mdi:silverware-clean","tags":["food / drink","silverware shimmer","cutlery clean"]},{"name":"mdi:silverware-fork","tags":["food / drink","cutlery fork"]},{"name":"mdi:silverware-spoon","tags":["food / drink","cutlery spoon"]},{"name":"mdi:silverware-variant","tags":["food / drink","places","cutlery variant"]},{"name":"mdi:sim-alert-outline","tags":["cellphone / phone","alert / error"]},{"name":"mdi:sim-off-outline","tags":["cellphone / phone"]},{"name":"mdi:sim-outline","tags":["cellphone / phone","sim card outline","subscriber identity module outline","subscriber identification module outline"]},{"name":"mdi:sine-wave","tags":["audio","alternating current","current ac","wave","analog","frequency","amplitude"]},{"name":"mdi:sitemap","tags":["workflow","flowchart"]},{"name":"mdi:sitemap-outline","tags":["workflow outline","flowchart outline"]},{"name":"mdi:size-l","tags":["size large"]},{"name":"mdi:size-m","tags":["size medium"]},{"name":"mdi:size-s","tags":["size small"]},{"name":"mdi:size-xl","tags":["size extra large"]},{"name":"mdi:size-xs","tags":["size extra small"]},{"name":"mdi:size-xxl","tags":["size extra extra large"]},{"name":"mdi:size-xxs","tags":["size extra extra small"]},{"name":"mdi:size-xxxl","tags":[]},{"name":"mdi:skate-off","tags":["sport"]},{"name":"mdi:skew-less","tags":["math","skew decrease"]},{"name":"mdi:skew-more","tags":["math","skew increase"]},{"name":"mdi:ski-water","tags":["sport","people / family","transportation + water","human ski water"]},{"name":"mdi:skip-backward","tags":["home automation","title backward","previous title"]},{"name":"mdi:skip-backward-outline","tags":[]},{"name":"mdi:skip-forward","tags":["home automation","title forward","next title"]},{"name":"mdi:skip-forward-outline","tags":[]},{"name":"mdi:skip-next-circle","tags":[]},{"name":"mdi:skip-next-circle-outline","tags":[]},{"name":"mdi:skip-next-outline","tags":[]},{"name":"mdi:skip-previous-circle","tags":[]},{"name":"mdi:skip-previous-circle-outline","tags":[]},{"name":"mdi:skip-previous-outline","tags":[]},{"name":"mdi:skull","tags":["holiday","gaming / rpg"]},{"name":"mdi:skull-crossbones","tags":["gaming / rpg","holiday","jolly roger"]},{"name":"mdi:skull-crossbones-outline","tags":["gaming / rpg","holiday","jolly roger outline"]},{"name":"mdi:skull-outline","tags":["holiday","gaming / rpg"]},{"name":"mdi:skull-scan","tags":["medical / hospital","x ray","radiology"]},{"name":"mdi:skull-scan-outline","tags":["medical / hospital","x ray outline","radiology outline"]},{"name":"mdi:slash-forward","tags":["math","divide","division"]},{"name":"mdi:slash-forward-box","tags":["math","divide box","division box"]},{"name":"mdi:sleep-off","tags":[]},{"name":"mdi:slide","tags":["playground slide"]},{"name":"mdi:slope-downhill","tags":[]},{"name":"mdi:slope-uphill","tags":[]},{"name":"mdi:slot-machine","tags":["casino","gambling"]},{"name":"mdi:slot-machine-outline","tags":["casino outline","gambling outline"]},{"name":"mdi:smart-card","tags":["account / user"]},{"name":"mdi:smart-card-off","tags":["account / user"]},{"name":"mdi:smart-card-off-outline","tags":["account / user"]},{"name":"mdi:smart-card-outline","tags":["account / user"]},{"name":"mdi:smart-card-reader","tags":["account / user"]},{"name":"mdi:smart-card-reader-outline","tags":["account / user"]},{"name":"mdi:smog","tags":[]},{"name":"mdi:smoke","tags":["smog","fire"]},{"name":"mdi:smoke-detector-alert","tags":["home automation","alert / error"]},{"name":"mdi:smoke-detector-alert-outline","tags":["home automation","alert / error"]},{"name":"mdi:smoke-detector-off","tags":["home automation"]},{"name":"mdi:smoke-detector-off-outline","tags":["home automation"]},{"name":"mdi:smoke-detector-outline","tags":["home automation"]},{"name":"mdi:smoke-detector-variant","tags":["home automation"]},{"name":"mdi:smoke-detector-variant-alert","tags":["home automation","alert / error"]},{"name":"mdi:smoke-detector-variant-off","tags":["home automation"]},{"name":"mdi:smoking-pipe","tags":[]},{"name":"mdi:smoking-pipe-off","tags":[]},{"name":"mdi:snail","tags":["animal","gastropod"]},{"name":"mdi:snake","tags":["animal","reptile"]},{"name":"mdi:snowflake-alert","tags":["weather","alert / error","home automation","cold alert","snow advisory","freeze advisory"]},{"name":"mdi:snowflake-check","tags":["weather","snowflake approve"]},{"name":"mdi:snowflake-melt","tags":["weather","defrost"]},{"name":"mdi:snowflake-off","tags":["weather"]},{"name":"mdi:snowflake-thermometer","tags":["weather","home automation","frost point","freezing point","snowflake temperature"]},{"name":"mdi:snowflake-variant","tags":["holiday","weather"]},{"name":"mdi:snowman","tags":["holiday"]},{"name":"mdi:soccer-field","tags":["sport","football pitch"]},{"name":"mdi:social-distance-2-meters","tags":["medical / hospital"]},{"name":"mdi:sofa","tags":["home automation","couch","living room","family room"]},{"name":"mdi:sofa-outline","tags":["home automation","couch outline","living room outline","family room outline"]},{"name":"mdi:sofa-single","tags":["home automation","loveseat","love seat","couch","chair accent","living room","family room"]},{"name":"mdi:sofa-single-outline","tags":["home automation","loveseat outline","love seat outline","couch outline","chair accent outline","living room outline","family room outline"]},{"name":"mdi:solar-panel","tags":["home automation","solar energy","solar electricity"]},{"name":"mdi:solar-panel-large","tags":["home automation","solar panel energy","solar panel electricity"]},{"name":"mdi:solar-power","tags":["home automation","solar energy","solar electricity"]},{"name":"mdi:soldering-iron","tags":[]},{"name":"mdi:solid","tags":[]},{"name":"mdi:sort","tags":["text / content / format"]},{"name":"mdi:sort-alphabetical-ascending","tags":["text / content / format"]},{"name":"mdi:sort-alphabetical-ascending-variant","tags":["text / content / format"]},{"name":"mdi:sort-alphabetical-descending","tags":["text / content / format"]},{"name":"mdi:sort-alphabetical-descending-variant","tags":["text / content / format"]},{"name":"mdi:sort-ascending","tags":["text / content / format"]},{"name":"mdi:sort-bool-ascending","tags":["text / content / format"]},{"name":"mdi:sort-bool-ascending-variant","tags":["text / content / format","sort checkbox ascending"]},{"name":"mdi:sort-bool-descending","tags":["text / content / format"]},{"name":"mdi:sort-bool-descending-variant","tags":["text / content / format","sort checkbox descending"]},{"name":"mdi:sort-calendar-ascending","tags":["text / content / format","date / time","sort date ascending"]},{"name":"mdi:sort-calendar-descending","tags":["text / content / format","date / time","sort date descending"]},{"name":"mdi:sort-clock-ascending","tags":["text / content / format","date / time","sort time ascending"]},{"name":"mdi:sort-clock-ascending-outline","tags":["text / content / format","date / time","sort time ascending outline"]},{"name":"mdi:sort-clock-descending","tags":["text / content / format","date / time","sort time descending"]},{"name":"mdi:sort-clock-descending-outline","tags":["text / content / format","date / time","sort time descending outline"]},{"name":"mdi:sort-descending","tags":["text / content / format"]},{"name":"mdi:sort-numeric-ascending","tags":["text / content / format"]},{"name":"mdi:sort-numeric-ascending-variant","tags":["text / content / format"]},{"name":"mdi:sort-numeric-descending","tags":["text / content / format"]},{"name":"mdi:sort-numeric-descending-variant","tags":["text / content / format"]},{"name":"mdi:sort-reverse-variant","tags":["text / content / format"]},{"name":"mdi:sort-variant-lock","tags":["text / content / format","lock"]},{"name":"mdi:sort-variant-lock-open","tags":["text / content / format","lock"]},{"name":"mdi:sort-variant-off","tags":["text / content / format"]},{"name":"mdi:sort-variant-remove","tags":["text / content / format"]},{"name":"mdi:soundbar","tags":["home automation","speaker bar"]},{"name":"mdi:source-branch","tags":["developer / languages"]},{"name":"mdi:source-branch-check","tags":["developer / languages"]},{"name":"mdi:source-branch-minus","tags":["developer / languages"]},{"name":"mdi:source-branch-plus","tags":["developer / languages"]},{"name":"mdi:source-branch-refresh","tags":["developer / languages"]},{"name":"mdi:source-branch-remove","tags":["developer / languages"]},{"name":"mdi:source-branch-sync","tags":["developer / languages"]},{"name":"mdi:source-commit","tags":[]},{"name":"mdi:source-commit-end","tags":[]},{"name":"mdi:source-commit-end-local","tags":[]},{"name":"mdi:source-commit-local","tags":[]},{"name":"mdi:source-commit-next-local","tags":[]},{"name":"mdi:source-commit-start","tags":[]},{"name":"mdi:source-commit-start-next-local","tags":[]},{"name":"mdi:source-fork","tags":["developer / languages"]},{"name":"mdi:source-merge","tags":["developer / languages"]},{"name":"mdi:source-pull","tags":["developer / languages"]},{"name":"mdi:source-repository","tags":["developer / languages"]},{"name":"mdi:source-repository-multiple","tags":["developer / languages","source repositories"]},{"name":"mdi:soy-sauce","tags":["food / drink","soya sauce"]},{"name":"mdi:soy-sauce-off","tags":[]},{"name":"mdi:space-invaders","tags":["gaming / rpg"]},{"name":"mdi:space-station","tags":[]},{"name":"mdi:spade","tags":["hardware / tools"]},{"name":"mdi:speaker-bluetooth","tags":["audio"]},{"name":"mdi:speaker-message","tags":["home automation","audio","text to speech"]},{"name":"mdi:speaker-multiple","tags":["audio","speakers"]},{"name":"mdi:speaker-off","tags":["audio","home automation"]},{"name":"mdi:speaker-pause","tags":["audio","music"]},{"name":"mdi:speaker-play","tags":["audio","music"]},{"name":"mdi:speaker-stop","tags":["audio","music"]},{"name":"mdi:speaker-wireless","tags":["audio","home automation"]},{"name":"mdi:spear","tags":["gaming / rpg","staff","fishing"]},{"name":"mdi:speedometer","tags":["automotive"]},{"name":"mdi:speedometer-medium","tags":["automotive"]},{"name":"mdi:speedometer-slow","tags":["automotive"]},{"name":"mdi:sphere","tags":["shape"]},{"name":"mdi:sphere-off","tags":["shape"]},{"name":"mdi:spider","tags":["holiday","nature","animal","arachnid","bug"]},{"name":"mdi:spider-outline","tags":["animal","holiday","nature","arachnid outline"]},{"name":"mdi:spider-thread","tags":["holiday","nature","animal","arachnid thread","bug"]},{"name":"mdi:spider-web","tags":["holiday","cobweb","arachnid web"]},{"name":"mdi:spirit-level","tags":["hardware / tools"]},{"name":"mdi:spoon-sugar","tags":["food / drink"]},{"name":"mdi:spotlight","tags":["home automation"]},{"name":"mdi:spotlight-beam","tags":["home automation"]},{"name":"mdi:spray","tags":["agriculture","drawing / art","color","paint","aerosol"]},{"name":"mdi:spray-bottle","tags":["cleaning"]},{"name":"mdi:sprinkler","tags":["home automation","agriculture","irrigation"]},{"name":"mdi:sprinkler-fire","tags":["home automation","agriculture","sprinkler mist","mister","sprinkler head"]},{"name":"mdi:sprinkler-variant","tags":["home automation","agriculture","irrigation"]},{"name":"mdi:sprout","tags":["agriculture","nature","seedling","plant","ecology","environment"]},{"name":"mdi:sprout-outline","tags":["agriculture","nature","seedling outline","plant outline","ecology outline","environment outline"]},{"name":"mdi:square","tags":["shape"]},{"name":"mdi:square-circle","tags":["food / drink","vegetarian","lacto vegetarian"]},{"name":"mdi:square-circle-outline","tags":[]},{"name":"mdi:square-edit-outline","tags":["edit / modify"]},{"name":"mdi:square-medium","tags":["shape"]},{"name":"mdi:square-medium-outline","tags":["shape"]},{"name":"mdi:square-off","tags":[]},{"name":"mdi:square-off-outline","tags":[]},{"name":"mdi:square-opacity","tags":["drawing / art","shape","square transparent"]},{"name":"mdi:square-outline","tags":["shape"]},{"name":"mdi:square-root","tags":["math"]},{"name":"mdi:square-root-box","tags":[]},{"name":"mdi:square-rounded","tags":[]},{"name":"mdi:square-rounded-badge","tags":["shape","notification","app badge","push notification"]},{"name":"mdi:square-rounded-badge-outline","tags":["shape","notification","app badge outline","push notification outline"]},{"name":"mdi:square-rounded-outline","tags":[]},{"name":"mdi:square-small","tags":["bullet"]},{"name":"mdi:square-wave","tags":["audio"]},{"name":"mdi:squeegee","tags":[]},{"name":"mdi:ssh","tags":[]},{"name":"mdi:stadium-variant","tags":["places","sport","arena"]},{"name":"mdi:stairs","tags":["transportation + other"]},{"name":"mdi:stairs-box","tags":[]},{"name":"mdi:stairs-down","tags":["transportation + other"]},{"name":"mdi:stairs-up","tags":["transportation + other"]},{"name":"mdi:stamper","tags":[]},{"name":"mdi:standard-definition","tags":["video / movie"]},{"name":"mdi:star-box","tags":["favorite box"]},{"name":"mdi:star-box-multiple","tags":["favorite box multiple"]},{"name":"mdi:star-box-multiple-outline","tags":["favorite box multiple outline"]},{"name":"mdi:star-box-outline","tags":["favorite box outline"]},{"name":"mdi:star-check","tags":["shape","favorite check"]},{"name":"mdi:star-check-outline","tags":["shape","favorite check outline"]},{"name":"mdi:star-cog","tags":["settings","favorite cog"]},{"name":"mdi:star-cog-outline","tags":["settings","favorite cog outline"]},{"name":"mdi:star-crescent","tags":["religion","islam","religion islamic","religion muslim"]},{"name":"mdi:star-david","tags":["religion","jewish","religion judaic","judaism","magen david"]},{"name":"mdi:star-four-points","tags":["shape"]},{"name":"mdi:star-four-points-box","tags":["shape","auto box"]},{"name":"mdi:star-four-points-box-outline","tags":["shape","auto box outline"]},{"name":"mdi:star-four-points-circle","tags":["shape","auto circle"]},{"name":"mdi:star-four-points-circle-outline","tags":["shape","auto circle outline"]},{"name":"mdi:star-four-points-outline","tags":["shape"]},{"name":"mdi:star-four-points-small","tags":["shape"]},{"name":"mdi:star-half","tags":["shape","favorite half"]},{"name":"mdi:star-minus","tags":["shape","favorite minus"]},{"name":"mdi:star-minus-outline","tags":["shape","favorite minus outline"]},{"name":"mdi:star-off","tags":["favorite off"]},{"name":"mdi:star-off-outline","tags":["favorite off outline"]},{"name":"mdi:star-plus","tags":["shape","favorite plus","star add","favorite add"]},{"name":"mdi:star-plus-outline","tags":["shape","star add outline","favorite plus outline","favorite add outline"]},{"name":"mdi:star-remove","tags":["shape","favorite remove"]},{"name":"mdi:star-remove-outline","tags":["shape","favorite remove outline"]},{"name":"mdi:star-settings","tags":["settings","favorite settings"]},{"name":"mdi:star-settings-outline","tags":["settings","favorite settings outline"]},{"name":"mdi:star-shooting","tags":["favorite shooting"]},{"name":"mdi:star-shooting-outline","tags":["favorite shooting outline"]},{"name":"mdi:star-three-points","tags":["shape"]},{"name":"mdi:star-three-points-outline","tags":["shape"]},{"name":"mdi:state-machine","tags":[]},{"name":"mdi:step-backward","tags":[]},{"name":"mdi:step-backward-2","tags":["frame backward"]},{"name":"mdi:step-forward","tags":[]},{"name":"mdi:step-forward-2","tags":["frame forward"]},{"name":"mdi:stethoscope","tags":["medical / hospital"]},{"name":"mdi:sticker","tags":[]},{"name":"mdi:sticker-alert","tags":["alert / error"]},{"name":"mdi:sticker-alert-outline","tags":["alert / error"]},{"name":"mdi:sticker-check","tags":[]},{"name":"mdi:sticker-check-outline","tags":[]},{"name":"mdi:sticker-circle-outline","tags":[]},{"name":"mdi:sticker-minus","tags":[]},{"name":"mdi:sticker-minus-outline","tags":[]},{"name":"mdi:sticker-outline","tags":[]},{"name":"mdi:sticker-plus","tags":[]},{"name":"mdi:sticker-plus-outline","tags":[]},{"name":"mdi:sticker-remove","tags":[]},{"name":"mdi:sticker-remove-outline","tags":[]},{"name":"mdi:sticker-text","tags":[]},{"name":"mdi:sticker-text-outline","tags":[]},{"name":"mdi:stocking","tags":["holiday"]},{"name":"mdi:stomach","tags":["medical / hospital"]},{"name":"mdi:stool","tags":[]},{"name":"mdi:stool-outline","tags":[]},{"name":"mdi:stop-circle","tags":[]},{"name":"mdi:stop-circle-outline","tags":[]},{"name":"mdi:store-alert","tags":["places","shopping","alert / error","shop alert"]},{"name":"mdi:store-alert-outline","tags":["places","shopping","alert / error","shop alert outline"]},{"name":"mdi:store-check","tags":["shopping","places","shop check","shop complete","store complete"]},{"name":"mdi:store-check-outline","tags":["shopping","places","shop complete","store complete outline","shop check outline"]},{"name":"mdi:store-clock","tags":["places","shopping","store schedule","store hours","shop clock","shop hours","shop schedule","store time","shop time"]},{"name":"mdi:store-clock-outline","tags":["places","shopping","date / time","shop clock outline","store hours outline","shop hours outline","store time outline","shop time outline","store schedule outline","shop schedule outline"]},{"name":"mdi:store-cog","tags":["places","shopping","settings","store settings","shop settings"]},{"name":"mdi:store-cog-outline","tags":["places","shopping","settings","store settings outline","shop settings outline","shop cog outline"]},{"name":"mdi:store-edit","tags":["places","shopping","edit / modify","shop edit"]},{"name":"mdi:store-edit-outline","tags":["places","shopping","edit / modify","shop edit outline"]},{"name":"mdi:store-marker","tags":["places","shopping","navigation","store location","shop marker","shop location"]},{"name":"mdi:store-marker-outline","tags":["places","shopping","navigation","store location outline","shop marker outline","shop location outline"]},{"name":"mdi:store-minus","tags":["places","shopping","shop minus"]},{"name":"mdi:store-minus-outline","tags":["places","shopping","shop minus outline"]},{"name":"mdi:store-off","tags":["places","shopping","shop off"]},{"name":"mdi:store-off-outline","tags":["places","shopping","shop off outline"]},{"name":"mdi:store-plus","tags":["places","shopping","shop plus"]},{"name":"mdi:store-plus-outline","tags":["places","shopping","shop plus outline"]},{"name":"mdi:store-remove","tags":["places","shopping","shop remove","store delete","shop delete"]},{"name":"mdi:store-remove-outline","tags":["places","shopping","shop remove outline","store delete outline","shop delete outline"]},{"name":"mdi:store-search","tags":["places","shopping","shop search","store find","shop find","store locator","shop locator","store look up","shop look up"]},{"name":"mdi:store-search-outline","tags":["places","shopping","store find outline","shop search outline","shop find outline","store locator outline","shop locator outline","store look up outline","shop look up outline"]},{"name":"mdi:store-settings","tags":["places","shopping","settings","shop settings"]},{"name":"mdi:store-settings-outline","tags":["places","shopping","settings","shop settings outline"]},{"name":"mdi:storefront","tags":["places","awning"]},{"name":"mdi:storefront-check","tags":[]},{"name":"mdi:storefront-check-outline","tags":[]},{"name":"mdi:storefront-edit","tags":["edit / modify"]},{"name":"mdi:storefront-edit-outline","tags":["edit / modify"]},{"name":"mdi:storefront-minus","tags":[]},{"name":"mdi:storefront-minus-outline","tags":[]},{"name":"mdi:storefront-plus","tags":[]},{"name":"mdi:storefront-plus-outline","tags":[]},{"name":"mdi:storefront-remove","tags":[]},{"name":"mdi:storefront-remove-outline","tags":[]},{"name":"mdi:stove","tags":["food / drink","home automation","cooker","oven"]},{"name":"mdi:strategy","tags":["sport","football play"]},{"name":"mdi:stretch-to-page","tags":["text / content / format","arrow"]},{"name":"mdi:stretch-to-page-outline","tags":["text / content / format","arrow"]},{"name":"mdi:string-lights","tags":["home automation","italian lights","christmas lights","fairy lights"]},{"name":"mdi:string-lights-off","tags":["home automation","italian lights off","christmas lights off","fairy lights off"]},{"name":"mdi:submarine","tags":[]},{"name":"mdi:subway-alert-variant","tags":["alert / error","transportation + other","subway warning variant"]},{"name":"mdi:summit","tags":["peak"]},{"name":"mdi:sun-angle","tags":["weather","solar angle"]},{"name":"mdi:sun-angle-outline","tags":["weather","solar angle outline"]},{"name":"mdi:sun-clock","tags":["weather","home automation","sun schedule","sun time","time of day"]},{"name":"mdi:sun-clock-outline","tags":["weather","home automation","date / time","sun schedule outline","sun time outline","time of day outline"]},{"name":"mdi:sun-compass","tags":["weather","home automation","navigation","sun azimuth","solar compass","solar asimuth"]},{"name":"mdi:sun-snowflake-variant","tags":["home automation","weather","hot cold","heat cool"]},{"name":"mdi:sun-thermometer","tags":["weather","home automation","heat index","sun temperature","day temperature","external temperature","outdoor temperature"]},{"name":"mdi:sun-thermometer-outline","tags":["home automation","weather","external temperature","outside temperature","heat index","day temperature"]},{"name":"mdi:sun-wireless","tags":["home automation","weather","weather sun wireless","illuminance","uv ray","ultraviolet"]},{"name":"mdi:sun-wireless-outline","tags":["home automation","weather","weather sun wireless outline","illuminance outline","uv ray outline","ultraviolet outline"]},{"name":"mdi:sunglasses","tags":["clothing"]},{"name":"mdi:surround-sound-2-0","tags":["audio","stereo"]},{"name":"mdi:surround-sound-2-1","tags":[]},{"name":"mdi:surround-sound-3-1","tags":["audio"]},{"name":"mdi:surround-sound-5-1","tags":["audio"]},{"name":"mdi:surround-sound-5-1-2","tags":[]},{"name":"mdi:surround-sound-7-1","tags":["audio"]},{"name":"mdi:swap-horizontal-circle-outline","tags":["arrow"]},{"name":"mdi:swap-vertical-circle","tags":["arrow"]},{"name":"mdi:swap-vertical-circle-outline","tags":["arrow"]},{"name":"mdi:swim","tags":["sport"]},{"name":"mdi:switch","tags":[]},{"name":"mdi:sword","tags":["gaming / rpg"]},{"name":"mdi:sword-cross","tags":["gaming / rpg"]},{"name":"mdi:syllabary-hangul","tags":["alpha / numeric","writing system hangul"]},{"name":"mdi:syllabary-hiragana","tags":["alpha / numeric","writing system hiragana"]},{"name":"mdi:syllabary-katakana","tags":["alpha / numeric","writing system katakana"]},{"name":"mdi:syllabary-katakana-halfwidth","tags":["alpha / numeric","writing system katakana half width"]},{"name":"mdi:symbol","tags":[]},{"name":"mdi:sync-circle","tags":[]},{"name":"mdi:tab-minus","tags":[]},{"name":"mdi:tab-plus","tags":["tab add"]},{"name":"mdi:tab-remove","tags":[]},{"name":"mdi:tab-search","tags":["tab find"]},{"name":"mdi:table","tags":["text / content / format"]},{"name":"mdi:table-account","tags":["account / user","table user"]},{"name":"mdi:table-alert","tags":["alert / error"]},{"name":"mdi:table-arrow-down","tags":["table download"]},{"name":"mdi:table-arrow-left","tags":["table import"]},{"name":"mdi:table-arrow-right","tags":["table share","table export"]},{"name":"mdi:table-arrow-up","tags":["table upload"]},{"name":"mdi:table-border","tags":["text / content / format"]},{"name":"mdi:table-cancel","tags":[]},{"name":"mdi:table-chair","tags":["home automation","restaurant","kitchen","dining","dining room"]},{"name":"mdi:table-check","tags":[]},{"name":"mdi:table-clock","tags":["date / time"]},{"name":"mdi:table-cog","tags":["settings","table settings"]},{"name":"mdi:table-column","tags":["text / content / format"]},{"name":"mdi:table-column-plus-after","tags":["text / content / format","table column add after"]},{"name":"mdi:table-column-plus-before","tags":["text / content / format","table column add before"]},{"name":"mdi:table-column-remove","tags":["text / content / format"]},{"name":"mdi:table-column-width","tags":["text / content / format"]},{"name":"mdi:table-edit","tags":["edit / modify","text / content / format"]},{"name":"mdi:table-eye","tags":[]},{"name":"mdi:table-eye-off","tags":[]},{"name":"mdi:table-filter","tags":[]},{"name":"mdi:table-furniture","tags":["home automation","kitchen","dining room"]},{"name":"mdi:table-headers-eye","tags":[]},{"name":"mdi:table-headers-eye-off","tags":[]},{"name":"mdi:table-heart","tags":["table favorite"]},{"name":"mdi:table-key","tags":[]},{"name":"mdi:table-large","tags":["text / content / format","geographic information system"]},{"name":"mdi:table-large-plus","tags":["text / content / format","geographic information system","table large add"]},{"name":"mdi:table-large-remove","tags":["text / content / format","geographic information system"]},{"name":"mdi:table-lock","tags":["lock"]},{"name":"mdi:table-minus","tags":[]},{"name":"mdi:table-multiple","tags":[]},{"name":"mdi:table-network","tags":[]},{"name":"mdi:table-off","tags":[]},{"name":"mdi:table-picnic","tags":[]},{"name":"mdi:table-pivot","tags":["text / content / format"]},{"name":"mdi:table-plus","tags":["text / content / format","table add"]},{"name":"mdi:table-question","tags":["table help"]},{"name":"mdi:table-refresh","tags":[]},{"name":"mdi:table-remove","tags":["text / content / format"]},{"name":"mdi:table-row","tags":["text / content / format"]},{"name":"mdi:table-row-height","tags":["text / content / format"]},{"name":"mdi:table-row-plus-after","tags":["text / content / format","table row add after"]},{"name":"mdi:table-row-plus-before","tags":["text / content / format","table row add before"]},{"name":"mdi:table-row-remove","tags":["text / content / format"]},{"name":"mdi:table-search","tags":[]},{"name":"mdi:table-settings","tags":["settings"]},{"name":"mdi:table-split-cell","tags":["text / content / format"]},{"name":"mdi:table-star","tags":["table favorite"]},{"name":"mdi:table-sync","tags":[]},{"name":"mdi:tablet-dashboard","tags":["device / tech"]},{"name":"mdi:taco","tags":["food / drink"]},{"name":"mdi:tag-arrow-down","tags":[]},{"name":"mdi:tag-arrow-down-outline","tags":[]},{"name":"mdi:tag-arrow-left","tags":[]},{"name":"mdi:tag-arrow-left-outline","tags":[]},{"name":"mdi:tag-arrow-right","tags":[]},{"name":"mdi:tag-arrow-right-outline","tags":[]},{"name":"mdi:tag-arrow-up","tags":[]},{"name":"mdi:tag-arrow-up-outline","tags":[]},{"name":"mdi:tag-check","tags":["tag approve"]},{"name":"mdi:tag-check-outline","tags":["tag approve outline"]},{"name":"mdi:tag-hidden","tags":[]},{"name":"mdi:tag-minus","tags":[]},{"name":"mdi:tag-minus-outline","tags":[]},{"name":"mdi:tag-multiple","tags":["tags"]},{"name":"mdi:tag-multiple-outline","tags":[]},{"name":"mdi:tag-off","tags":[]},{"name":"mdi:tag-off-outline","tags":[]},{"name":"mdi:tag-plus","tags":["tag add"]},{"name":"mdi:tag-plus-outline","tags":[]},{"name":"mdi:tag-remove","tags":[]},{"name":"mdi:tag-remove-outline","tags":[]},{"name":"mdi:tag-search","tags":["tag find"]},{"name":"mdi:tag-search-outline","tags":["tag find outline"]},{"name":"mdi:tag-text","tags":[]},{"name":"mdi:tag-text-outline","tags":[]},{"name":"mdi:tally-mark-1","tags":["math","counting 1","one"]},{"name":"mdi:tally-mark-2","tags":["math","counting 2","two"]},{"name":"mdi:tally-mark-3","tags":["math","counting 3","three"]},{"name":"mdi:tally-mark-4","tags":["math","counting 4","four"]},{"name":"mdi:tally-mark-5","tags":["math","counting 5","five"]},{"name":"mdi:tangram","tags":["gaming / rpg","puzzle"]},{"name":"mdi:tank","tags":[]},{"name":"mdi:tanker-truck","tags":["transportation + road","fuel truck","oil truck","water truck","tanker"]},{"name":"mdi:tape-drive","tags":[]},{"name":"mdi:tape-measure","tags":["hardware / tools","measuring tape"]},{"name":"mdi:target","tags":["registration mark"]},{"name":"mdi:target-account","tags":["account / user","crosshairs account","target user"]},{"name":"mdi:target-variant","tags":["registration mark"]},{"name":"mdi:tea-outline","tags":["food / drink"]},{"name":"mdi:teddy-bear","tags":["holiday","home automation","child toy","children toy","kids room","childrens room","play room"]},{"name":"mdi:telescope","tags":["science"]},{"name":"mdi:television-ambient-light","tags":["home automation"]},{"name":"mdi:television-classic","tags":["device / tech","home automation","tv classic"]},{"name":"mdi:television-classic-off","tags":["device / tech","home automation","tv classic off"]},{"name":"mdi:television-guide","tags":["device / tech","home automation"]},{"name":"mdi:television-off","tags":["device / tech","home automation","tv off"]},{"name":"mdi:television-pause","tags":["device / tech"]},{"name":"mdi:television-shimmer","tags":["device / tech","television clean"]},{"name":"mdi:television-speaker","tags":["audio","video / movie"]},{"name":"mdi:television-speaker-off","tags":["audio","video / movie"]},{"name":"mdi:television-stop","tags":["device / tech"]},{"name":"mdi:temperature-celsius","tags":["weather","temperature centigrade"]},{"name":"mdi:temperature-fahrenheit","tags":["weather"]},{"name":"mdi:temperature-kelvin","tags":["weather"]},{"name":"mdi:tennis-ball-outline","tags":["sport"]},{"name":"mdi:tent","tags":["camping"]},{"name":"mdi:test-tube","tags":["science"]},{"name":"mdi:test-tube-empty","tags":["science"]},{"name":"mdi:test-tube-off","tags":["science"]},{"name":"mdi:text-account","tags":["account / user","biography","text user"]},{"name":"mdi:text-box-check","tags":["files / folders","file document box tick","file document box check"]},{"name":"mdi:text-box-check-outline","tags":["files / folders","file document box tick outline","file document box check outline"]},{"name":"mdi:text-box-edit","tags":["files / folders","edit / modify"]},{"name":"mdi:text-box-edit-outline","tags":["files / folders","edit / modify"]},{"name":"mdi:text-box-minus","tags":["files / folders","file document box minus"]},{"name":"mdi:text-box-minus-outline","tags":["files / folders","file document box minus outline"]},{"name":"mdi:text-box-multiple","tags":["files / folders","file document boxes","file document box multiple"]},{"name":"mdi:text-box-multiple-outline","tags":["files / folders","file document boxes outline","file document box multiple outline"]},{"name":"mdi:text-box-outline","tags":["files / folders","file document box outline"]},{"name":"mdi:text-box-plus","tags":["files / folders","file document box plus"]},{"name":"mdi:text-box-plus-outline","tags":["files / folders","file document box plus outline"]},{"name":"mdi:text-box-remove","tags":["files / folders","file document box remove"]},{"name":"mdi:text-box-remove-outline","tags":["files / folders","file document box remove outline"]},{"name":"mdi:text-box-search","tags":["files / folders","file document box search"]},{"name":"mdi:text-box-search-outline","tags":["files / folders","file document box search outline"]},{"name":"mdi:text-recognition","tags":[]},{"name":"mdi:text-search","tags":["notes search"]},{"name":"mdi:text-search-variant","tags":["notes search variant"]},{"name":"mdi:text-shadow","tags":[]},{"name":"mdi:texture-box","tags":["math","surface area"]},{"name":"mdi:theater","tags":["places","home automation","cinema","theatre"]},{"name":"mdi:theme-light-dark","tags":["weather","sun moon stars"]},{"name":"mdi:thermometer","tags":["weather","home automation","automotive","temperature"]},{"name":"mdi:thermometer-alert","tags":["home automation","weather","alert / error","thermometer warning","temperature alert","temperature warning"]},{"name":"mdi:thermometer-auto","tags":["home automation","weather","temperature auto"]},{"name":"mdi:thermometer-bluetooth","tags":["weather","home automation","automotive","temperature bluetooth"]},{"name":"mdi:thermometer-check","tags":["weather","home automation","thermometer approve","temperature check","temperature approve"]},{"name":"mdi:thermometer-chevron-down","tags":["home automation","weather","temperature chevron down","temperature decrease","thermometer decrease"]},{"name":"mdi:thermometer-chevron-up","tags":["home automation","weather","temperature chevron up","temperature increase","thermometer increase"]},{"name":"mdi:thermometer-high","tags":["home automation","weather","temperature high"]},{"name":"mdi:thermometer-lines","tags":["weather","home automation","temperature lines"]},{"name":"mdi:thermometer-low","tags":["home automation","weather","temperature low"]},{"name":"mdi:thermometer-minus","tags":["home automation","weather","temperature minus","thermometer decrease","temperature decrease"]},{"name":"mdi:thermometer-off","tags":["weather","home automation","temperature off"]},{"name":"mdi:thermometer-plus","tags":["home automation","weather","thermometer add","thermometer increase","temperature plus","temperature add","temperature increase"]},{"name":"mdi:thermometer-probe","tags":[]},{"name":"mdi:thermometer-probe-off","tags":[]},{"name":"mdi:thermometer-water","tags":["weather","home automation","dew point","water temperature","boiling point"]},{"name":"mdi:thermostat-auto","tags":["home automation"]},{"name":"mdi:thermostat-box","tags":["home automation","device / tech"]},{"name":"mdi:thermostat-box-auto","tags":["home automation"]},{"name":"mdi:thermostat-cog","tags":[]},{"name":"mdi:thought-bubble","tags":["comic bubble","thinking"]},{"name":"mdi:thought-bubble-outline","tags":["comic thought bubble outline","thinking outline","think outline"]},{"name":"mdi:ticket-account","tags":["account / user","ticket user"]},{"name":"mdi:ticket-outline","tags":[]},{"name":"mdi:ticket-percent","tags":["coupon","voucher"]},{"name":"mdi:ticket-percent-outline","tags":["coupon outline","voucher outline"]},{"name":"mdi:tie","tags":["clothing"]},{"name":"mdi:tilde","tags":[]},{"name":"mdi:tilde-off","tags":[]},{"name":"mdi:timeline","tags":[]},{"name":"mdi:timeline-alert","tags":["alert / error"]},{"name":"mdi:timeline-alert-outline","tags":["alert / error"]},{"name":"mdi:timeline-check","tags":[]},{"name":"mdi:timeline-check-outline","tags":[]},{"name":"mdi:timeline-clock","tags":["date / time"]},{"name":"mdi:timeline-clock-outline","tags":["date / time"]},{"name":"mdi:timeline-minus","tags":[]},{"name":"mdi:timeline-minus-outline","tags":[]},{"name":"mdi:timeline-outline","tags":[]},{"name":"mdi:timeline-plus","tags":[]},{"name":"mdi:timeline-plus-outline","tags":[]},{"name":"mdi:timeline-question","tags":["timeline help"]},{"name":"mdi:timeline-question-outline","tags":["timeline help outline"]},{"name":"mdi:timeline-remove","tags":[]},{"name":"mdi:timeline-remove-outline","tags":[]},{"name":"mdi:timeline-text","tags":[]},{"name":"mdi:timeline-text-outline","tags":[]},{"name":"mdi:timer","tags":["sport","date / time","stopwatch"]},{"name":"mdi:timer-alert","tags":["date / time","alert / error","stopwatch alert"]},{"name":"mdi:timer-alert-outline","tags":["date / time","alert / error","stopwatch alert outline"]},{"name":"mdi:timer-cancel","tags":["date / time","stopwatch cancel"]},{"name":"mdi:timer-cancel-outline","tags":["date / time","stopwatch cancel outline"]},{"name":"mdi:timer-check","tags":["date / time","stopwatch check","timer tick","stopwatch tick"]},{"name":"mdi:timer-check-outline","tags":["date / time","timer tick outline","stopwatch check outline","stopwatch tick outline"]},{"name":"mdi:timer-cog","tags":["date / time","settings","timer settings"]},{"name":"mdi:timer-cog-outline","tags":["date / time","settings","timer settings outline"]},{"name":"mdi:timer-edit","tags":["date / time","edit / modify","stopwatch edit"]},{"name":"mdi:timer-edit-outline","tags":["date / time","edit / modify","stopwatch edit outline"]},{"name":"mdi:timer-lock","tags":["date / time","lock","stopwatch lock","timer secure","stopwatch secure"]},{"name":"mdi:timer-lock-open","tags":["date / time","lock","stopwatch lock open"]},{"name":"mdi:timer-lock-open-outline","tags":["date / time","lock","stopwatch lock open outline"]},{"name":"mdi:timer-lock-outline","tags":["date / time","lock","stopwatch lock outline","stopwatch secure outline","timer secure outline"]},{"name":"mdi:timer-marker","tags":["date / time","navigation","stopwatch marker","timer location","stopwatch location"]},{"name":"mdi:timer-marker-outline","tags":["date / time","navigation","stopwatch marker outline","timer location outline","stopwatch location outline"]},{"name":"mdi:timer-minus","tags":["date / time","timer subtract","stopwatch minus","stopwatch subtract"]},{"name":"mdi:timer-minus-outline","tags":["date / time","timer subtract outline","stopwatch minus outline","stopwatch subtract outline"]},{"name":"mdi:timer-music","tags":["date / time","music","stopwatch music"]},{"name":"mdi:timer-music-outline","tags":["date / time","music","stopwatch music outline"]},{"name":"mdi:timer-off","tags":["date / time","stopwatch off"]},{"name":"mdi:timer-pause","tags":["date / time","stopwatch pause"]},{"name":"mdi:timer-pause-outline","tags":["date / time","stopwatch pause outline"]},{"name":"mdi:timer-play","tags":["date / time","timer start","stopwatch play","stopwatch start"]},{"name":"mdi:timer-play-outline","tags":["date / time","timer start outline","stopwatch play outline","stopwatch start outline"]},{"name":"mdi:timer-plus","tags":["date / time","timer add","stopwatch plus","stopwatch add"]},{"name":"mdi:timer-plus-outline","tags":["date / time","timer add outline","stopwatch plus outline","stopwatch add outline"]},{"name":"mdi:timer-refresh","tags":["date / time","stopwatch refresh"]},{"name":"mdi:timer-refresh-outline","tags":["date / time","stopwatch refresh outline"]},{"name":"mdi:timer-remove","tags":["date / time","stopwatch remove"]},{"name":"mdi:timer-remove-outline","tags":["date / time","stopwatch remove outline"]},{"name":"mdi:timer-sand","tags":["date / time","hourglass"]},{"name":"mdi:timer-sand-complete","tags":["date / time","hourglass complete"]},{"name":"mdi:timer-sand-paused","tags":["date / time","hourglass paused"]},{"name":"mdi:timer-settings","tags":["date / time","settings"]},{"name":"mdi:timer-settings-outline","tags":["date / time","settings"]},{"name":"mdi:timer-star","tags":["date / time","timer favorite","stopwatch star","stopwatch favorite"]},{"name":"mdi:timer-star-outline","tags":["date / time","timer favorite outline","stopwatch star outline","stopwatch favorite outline"]},{"name":"mdi:timer-stop","tags":["date / time","stopwatch stop"]},{"name":"mdi:timer-stop-outline","tags":["date / time","stopwatch stop outline"]},{"name":"mdi:timer-sync","tags":["date / time","stopwatch sync"]},{"name":"mdi:timer-sync-outline","tags":["date / time","stopwatch sync outline"]},{"name":"mdi:timetable","tags":["date / time"]},{"name":"mdi:tire","tags":["automotive","agriculture","tyre","wheel"]},{"name":"mdi:toaster","tags":["home automation"]},{"name":"mdi:toaster-off","tags":["home automation"]},{"name":"mdi:toaster-oven","tags":["home automation","food / drink"]},{"name":"mdi:toggle-switch-variant","tags":["home automation","light switch on"]},{"name":"mdi:toggle-switch-variant-off","tags":["home automation","light switch off","rocker switch off"]},{"name":"mdi:toilet","tags":["home automation","bathroom","lavatory","bidet"]},{"name":"mdi:tools","tags":["hardware / tools","wrench","screwdriver"]},{"name":"mdi:tooltip","tags":["tooltip"]},{"name":"mdi:tooltip-cellphone","tags":["cellphone / phone","tooltip","cellphone location","cellphone gps","find my phone"]},{"name":"mdi:tooltip-check","tags":["tooltip"]},{"name":"mdi:tooltip-check-outline","tags":["tooltip"]},{"name":"mdi:tooltip-edit","tags":["tooltip","edit / modify"]},{"name":"mdi:tooltip-edit-outline","tags":["edit / modify","tooltip"]},{"name":"mdi:tooltip-image","tags":["tooltip"]},{"name":"mdi:tooltip-image-outline","tags":["tooltip"]},{"name":"mdi:tooltip-minus","tags":["tooltip"]},{"name":"mdi:tooltip-minus-outline","tags":["tooltip"]},{"name":"mdi:tooltip-outline","tags":["tooltip"]},{"name":"mdi:tooltip-plus","tags":["tooltip","tooltip add"]},{"name":"mdi:tooltip-plus-outline","tags":["tooltip","tooltip outline plus","tooltip add outline"]},{"name":"mdi:tooltip-question","tags":["tooltip","tooltip help"]},{"name":"mdi:tooltip-question-outline","tags":["tooltip","tooltip help outline"]},{"name":"mdi:tooltip-remove","tags":["tooltip"]},{"name":"mdi:tooltip-remove-outline","tags":["tooltip"]},{"name":"mdi:tooltip-text","tags":["tooltip"]},{"name":"mdi:tooltip-text-outline","tags":["tooltip"]},{"name":"mdi:tooth","tags":["medical / hospital","dentist"]},{"name":"mdi:tooth-outline","tags":["medical / hospital"]},{"name":"mdi:toothbrush","tags":["medical / hospital","dentist","oral hygiene"]},{"name":"mdi:toothbrush-electric","tags":["medical / hospital","dentist","oral hygiene"]},{"name":"mdi:toothbrush-paste","tags":["medical / hospital","dentist","oral hygiene"]},{"name":"mdi:torch","tags":["sport","olympics"]},{"name":"mdi:tortoise","tags":["animal","turtle","reptile"]},{"name":"mdi:toslink","tags":["audio","optical audio"]},{"name":"mdi:touch-text-outline","tags":[]},{"name":"mdi:tournament","tags":["gaming / rpg","sport","bracket"]},{"name":"mdi:tower-beach","tags":[]},{"name":"mdi:tower-fire","tags":[]},{"name":"mdi:town-hall","tags":["places","school"]},{"name":"mdi:toy-brick","tags":["lego","plugin","extension"]},{"name":"mdi:toy-brick-marker","tags":["navigation","lego","plugin","extension","lego location","toy brick location"]},{"name":"mdi:toy-brick-marker-outline","tags":["navigation","extension outline","lego location outline","toy brick location outline","plugin outline","lego outline"]},{"name":"mdi:toy-brick-minus","tags":["lego","plugin","extension"]},{"name":"mdi:toy-brick-minus-outline","tags":["lego","plugin","extension"]},{"name":"mdi:toy-brick-outline","tags":["lego","plugin","extension"]},{"name":"mdi:toy-brick-plus","tags":["lego","plugin","extension"]},{"name":"mdi:toy-brick-plus-outline","tags":["lego","plugin","extension"]},{"name":"mdi:toy-brick-remove","tags":["lego","plugin","extension"]},{"name":"mdi:toy-brick-remove-outline","tags":["lego","plugin","extension"]},{"name":"mdi:toy-brick-search","tags":["lego","plugin","extension"]},{"name":"mdi:toy-brick-search-outline","tags":["lego","plugin","extension"]},{"name":"mdi:track-light","tags":["home automation"]},{"name":"mdi:track-light-off","tags":[]},{"name":"mdi:trackpad","tags":[]},{"name":"mdi:trackpad-lock","tags":["lock"]},{"name":"mdi:tractor","tags":["agriculture","transportation + road","farm"]},{"name":"mdi:trademark","tags":["tm"]},{"name":"mdi:traffic-cone","tags":["transportation + road"]},{"name":"mdi:train-car-autorack","tags":["transportation + other"]},{"name":"mdi:train-car-box","tags":["transportation + other"]},{"name":"mdi:train-car-box-full","tags":["transportation + other"]},{"name":"mdi:train-car-box-open","tags":["transportation + other"]},{"name":"mdi:train-car-caboose","tags":["transportation + other"]},{"name":"mdi:train-car-centerbeam","tags":["transportation + other"]},{"name":"mdi:train-car-centerbeam-full","tags":["transportation + other"]},{"name":"mdi:train-car-container","tags":["transportation + other"]},{"name":"mdi:train-car-flatbed","tags":["transportation + other"]},{"name":"mdi:train-car-flatbed-car","tags":["transportation + other"]},{"name":"mdi:train-car-flatbed-tank","tags":["transportation + other"]},{"name":"mdi:train-car-gondola","tags":["transportation + other"]},{"name":"mdi:train-car-gondola-full","tags":["transportation + other"]},{"name":"mdi:train-car-hopper","tags":["transportation + other"]},{"name":"mdi:train-car-hopper-covered","tags":["transportation + other"]},{"name":"mdi:train-car-hopper-full","tags":["transportation + other"]},{"name":"mdi:train-car-intermodal","tags":["transportation + other"]},{"name":"mdi:train-car-passenger","tags":["transportation + other"]},{"name":"mdi:train-car-passenger-door","tags":["transportation + other"]},{"name":"mdi:train-car-passenger-door-open","tags":["transportation + other"]},{"name":"mdi:train-car-passenger-variant","tags":["transportation + other"]},{"name":"mdi:train-car-tank","tags":["transportation + other"]},{"name":"mdi:transcribe","tags":[]},{"name":"mdi:transcribe-close","tags":[]},{"name":"mdi:transfer","tags":[]},{"name":"mdi:transfer-down","tags":["arrow"]},{"name":"mdi:transfer-left","tags":["arrow"]},{"name":"mdi:transfer-right","tags":["arrow"]},{"name":"mdi:transfer-up","tags":["arrow"]},{"name":"mdi:transit-connection","tags":["transportation + other","navigation"]},{"name":"mdi:transit-connection-horizontal","tags":["transportation + other"]},{"name":"mdi:transit-connection-variant","tags":["transportation + other","navigation"]},{"name":"mdi:transit-detour","tags":["transportation + other","navigation"]},{"name":"mdi:transit-skip","tags":["transportation + other"]},{"name":"mdi:translate-off","tags":[]},{"name":"mdi:translate-variant","tags":["developer / languages","spoken language"]},{"name":"mdi:transmission-tower","tags":["home automation","pylon","powerline","electricity","energy","power","grid"]},{"name":"mdi:transmission-tower-export","tags":["home automation","power from grid","energy from grid","electricity from grid"]},{"name":"mdi:transmission-tower-import","tags":["home automation","power to grid","energy to grid","electricity to grid","return to grid"]},{"name":"mdi:transmission-tower-off","tags":["home automation","powerline off","pylon off","grid off"]},{"name":"mdi:trash-can","tags":["delete","rubbish bin","trashcan","garbage can"]},{"name":"mdi:trash-can-outline","tags":["delete outline","rubbish bin outline","trashcan outline","garbage can outline"]},{"name":"mdi:tray","tags":["queue","printer","inbox"]},{"name":"mdi:tray-alert","tags":["alert / error","queue","printer","inbox"]},{"name":"mdi:tray-arrow-down","tags":["arrow","tray download"]},{"name":"mdi:tray-arrow-up","tags":["arrow","tray upload"]},{"name":"mdi:tray-full","tags":["queue","printer","inbox"]},{"name":"mdi:tray-minus","tags":["queue","printer","inbox"]},{"name":"mdi:tray-plus","tags":["queue","printer","inbox"]},{"name":"mdi:tray-remove","tags":["queue","printer","inbox"]},{"name":"mdi:treasure-chest","tags":["gaming / rpg","shopping","lock","jewelry box","jewel case"]},{"name":"mdi:treasure-chest-outline","tags":["gaming / rpg","lock","shopping","jewel case outline","jewelry box outline"]},{"name":"mdi:tree","tags":["nature","agriculture","plant"]},{"name":"mdi:tree-outline","tags":["nature","agriculture","plant"]},{"name":"mdi:triangle","tags":["shape"]},{"name":"mdi:triangle-down","tags":["shape"]},{"name":"mdi:triangle-down-outline","tags":["shape"]},{"name":"mdi:triangle-outline","tags":["shape"]},{"name":"mdi:triangle-small-down","tags":["shape","trending down variant"]},{"name":"mdi:triangle-small-up","tags":["shape","trending up variant"]},{"name":"mdi:triangle-wave","tags":["audio"]},{"name":"mdi:triforce","tags":["gaming / rpg","zelda"]},{"name":"mdi:trophy","tags":["sport","achievement"]},{"name":"mdi:trophy-award","tags":["sport","achievement award"]},{"name":"mdi:trophy-broken","tags":["sport"]},{"name":"mdi:trophy-outline","tags":["sport","achievement outline"]},{"name":"mdi:trophy-variant","tags":["sport","achievement variant"]},{"name":"mdi:trophy-variant-outline","tags":["sport","achievement variant outline"]},{"name":"mdi:truck-alert","tags":["transportation + road","alert / error","truck error"]},{"name":"mdi:truck-alert-outline","tags":["transportation + road","alert / error","truck error outline"]},{"name":"mdi:truck-cargo-container","tags":["transportation + road","truck shipping"]},{"name":"mdi:truck-check","tags":["transportation + road","truck tick","lorry check","courier check"]},{"name":"mdi:truck-check-outline","tags":["transportation + road"]},{"name":"mdi:truck-delivery","tags":["transportation + road","lorry delivery"]},{"name":"mdi:truck-delivery-outline","tags":["transportation + road"]},{"name":"mdi:truck-fast","tags":["transportation + road","lorry fast","courier fast"]},{"name":"mdi:truck-fast-outline","tags":["transportation + road"]},{"name":"mdi:truck-flatbed","tags":["automotive","transportation + road","truck flatbed tow"]},{"name":"mdi:truck-minus","tags":["transportation + road","truck subtract"]},{"name":"mdi:truck-minus-outline","tags":["transportation + road","truck subtract outline"]},{"name":"mdi:truck-plus","tags":["transportation + road","medical / hospital","truck add"]},{"name":"mdi:truck-plus-outline","tags":["transportation + road","medical / hospital","truck add outline"]},{"name":"mdi:truck-remove","tags":["transportation + road"]},{"name":"mdi:truck-remove-outline","tags":["transportation + road"]},{"name":"mdi:truck-snowflake","tags":["transportation + road","truck refrigerator","truck freezer"]},{"name":"mdi:truck-trailer","tags":["transportation + road"]},{"name":"mdi:trumpet","tags":["music"]},{"name":"mdi:tshirt-crew","tags":["clothing","t shirt crew"]},{"name":"mdi:tshirt-crew-outline","tags":["clothing","t shirt crew outline"]},{"name":"mdi:tshirt-v","tags":["clothing","t shirt v"]},{"name":"mdi:tshirt-v-outline","tags":["clothing","t shirt v outline"]},{"name":"mdi:tumble-dryer","tags":["home automation","laundry room"]},{"name":"mdi:tumble-dryer-alert","tags":["home automation","alert / error","laundry room alert"]},{"name":"mdi:tumble-dryer-off","tags":["home automation","laundry room off"]},{"name":"mdi:tune-variant","tags":["audio","settings","settings","equalizer"]},{"name":"mdi:tune-vertical-variant","tags":["audio","settings","settings vertical","equalizer vertical"]},{"name":"mdi:tunnel","tags":["transportation + road","transportation + other"]},{"name":"mdi:tunnel-outline","tags":["transportation + road","transportation + other"]},{"name":"mdi:turbine","tags":["transportation + flying","jet engine","wind turbine"]},{"name":"mdi:turkey","tags":["animal","holiday","agriculture","thanksgiving"]},{"name":"mdi:turnstile","tags":[]},{"name":"mdi:turnstile-outline","tags":[]},{"name":"mdi:turtle","tags":["animal","reptile"]},{"name":"mdi:two-factor-authentication","tags":[]},{"name":"mdi:typewriter","tags":[]},{"name":"mdi:ufo","tags":["unidentified flying object","alien"]},{"name":"mdi:ufo-outline","tags":["unidentified flying object outline","alien"]},{"name":"mdi:ultra-high-definition","tags":["video / movie","uhd"]},{"name":"mdi:umbrella","tags":["weather"]},{"name":"mdi:umbrella-closed","tags":["weather"]},{"name":"mdi:umbrella-closed-outline","tags":["weather"]},{"name":"mdi:umbrella-closed-variant","tags":["weather"]},{"name":"mdi:umbrella-outline","tags":["weather"]},{"name":"mdi:undo-variant","tags":["arrow"]},{"name":"mdi:unfold-less-vertical","tags":["chevron right left","collapse vertical"]},{"name":"mdi:unfold-more-vertical","tags":["chevron left right","expand vertical"]},{"name":"mdi:ungroup","tags":[]},{"name":"mdi:unicorn","tags":["animal","fantasy"]},{"name":"mdi:unicorn-variant","tags":["animal","fantasy variant"]},{"name":"mdi:unicycle","tags":["sport","transportation + other"]},{"name":"mdi:upload-lock","tags":["lock"]},{"name":"mdi:upload-lock-outline","tags":["lock"]},{"name":"mdi:upload-multiple","tags":["uploads"]},{"name":"mdi:upload-network","tags":[]},{"name":"mdi:upload-network-outline","tags":[]},{"name":"mdi:upload-off","tags":[]},{"name":"mdi:upload-off-outline","tags":[]},{"name":"mdi:upload-outline","tags":["file upload outline"]},{"name":"mdi:usb-flash-drive","tags":[]},{"name":"mdi:usb-flash-drive-outline","tags":[]},{"name":"mdi:usb-port","tags":[]},{"name":"mdi:valve","tags":["home automation"]},{"name":"mdi:valve-closed","tags":["home automation"]},{"name":"mdi:valve-open","tags":["home automation"]},{"name":"mdi:van-passenger","tags":["transportation + road"]},{"name":"mdi:van-utility","tags":["transportation + road","van candy"]},{"name":"mdi:vanish","tags":[]},{"name":"mdi:vanish-quarter","tags":[]},{"name":"mdi:vanity-light","tags":["home automation"]},{"name":"mdi:variable","tags":["developer / languages","math"]},{"name":"mdi:variable-box","tags":["developer / languages"]},{"name":"mdi:vector-arrange-above","tags":["vector","arrange","geographic information system"]},{"name":"mdi:vector-arrange-below","tags":["vector","arrange","geographic information system"]},{"name":"mdi:vector-bezier","tags":["vector"]},{"name":"mdi:vector-circle","tags":["vector","geographic information system"]},{"name":"mdi:vector-circle-variant","tags":["vector"]},{"name":"mdi:vector-combine","tags":["vector","geographic information system"]},{"name":"mdi:vector-curve","tags":["vector","geographic information system","bezier"]},{"name":"mdi:vector-difference","tags":["vector","geographic information system"]},{"name":"mdi:vector-difference-ab","tags":["vector","geographic information system"]},{"name":"mdi:vector-difference-ba","tags":["vector","geographic information system"]},{"name":"mdi:vector-ellipse","tags":["vector","geographic information system"]},{"name":"mdi:vector-intersection","tags":["vector","geographic information system"]},{"name":"mdi:vector-line","tags":["vector","geographic information system"]},{"name":"mdi:vector-link","tags":["vector","geographic information system"]},{"name":"mdi:vector-point","tags":["vector"]},{"name":"mdi:vector-point-edit","tags":["vector","edit / modify"]},{"name":"mdi:vector-point-minus","tags":["vector"]},{"name":"mdi:vector-point-plus","tags":["vector","vector point add"]},{"name":"mdi:vector-point-select","tags":["vector","geographic information system"]},{"name":"mdi:vector-polygon","tags":["vector","geographic information system"]},{"name":"mdi:vector-polygon-variant","tags":["vector"]},{"name":"mdi:vector-polyline","tags":["vector","geographic information system"]},{"name":"mdi:vector-polyline-edit","tags":["edit / modify"]},{"name":"mdi:vector-polyline-minus","tags":[]},{"name":"mdi:vector-polyline-plus","tags":[]},{"name":"mdi:vector-polyline-remove","tags":[]},{"name":"mdi:vector-radius","tags":["vector","geographic information system"]},{"name":"mdi:vector-rectangle","tags":["vector","geographic information system"]},{"name":"mdi:vector-selection","tags":["vector","geographic information system"]},{"name":"mdi:vector-square","tags":["vector","geographic information system","mdi"]},{"name":"mdi:vector-square-close","tags":["vector"]},{"name":"mdi:vector-square-edit","tags":["vector","edit / modify"]},{"name":"mdi:vector-square-minus","tags":["vector","vector square subtract"]},{"name":"mdi:vector-square-open","tags":["vector"]},{"name":"mdi:vector-square-plus","tags":["vector","vector square add"]},{"name":"mdi:vector-square-remove","tags":["vector","vector square delete"]},{"name":"mdi:vector-triangle","tags":["vector","geographic information system"]},{"name":"mdi:vector-union","tags":["vector","geographic information system"]},{"name":"mdi:vhs","tags":["video / movie","video home system","vhs cassette","vhs tape"]},{"name":"mdi:vibrate-off","tags":[]},{"name":"mdi:video-2d","tags":["video / movie"]},{"name":"mdi:video-3d","tags":["video / movie"]},{"name":"mdi:video-3d-off","tags":["video / movie"]},{"name":"mdi:video-3d-variant","tags":["video / movie"]},{"name":"mdi:video-check","tags":["video / movie"]},{"name":"mdi:video-check-outline","tags":["video / movie"]},{"name":"mdi:video-high-definition","tags":["video / movie"]},{"name":"mdi:video-input-scart","tags":["video / movie"]},{"name":"mdi:video-marker","tags":["video / movie","navigation","video location"]},{"name":"mdi:video-marker-outline","tags":["video / movie","navigation","video location outline"]},{"name":"mdi:video-minus-outline","tags":["video / movie"]},{"name":"mdi:video-plus-outline","tags":["video / movie"]},{"name":"mdi:video-vintage","tags":["video / movie","video film","video classic"]},{"name":"mdi:video-wireless","tags":["video / movie"]},{"name":"mdi:video-wireless-outline","tags":["video / movie"]},{"name":"mdi:view-dashboard-edit","tags":["view","edit / modify"]},{"name":"mdi:view-dashboard-edit-outline","tags":["view","edit / modify"]},{"name":"mdi:view-dashboard-variant-outline","tags":["view"]},{"name":"mdi:view-gallery","tags":["view"]},{"name":"mdi:view-gallery-outline","tags":["view"]},{"name":"mdi:view-grid","tags":["view"]},{"name":"mdi:view-grid-compact","tags":[]},{"name":"mdi:view-grid-outline","tags":["view"]},{"name":"mdi:view-grid-plus-outline","tags":["view"]},{"name":"mdi:view-parallel","tags":["view"]},{"name":"mdi:view-parallel-outline","tags":["view"]},{"name":"mdi:view-sequential","tags":["view"]},{"name":"mdi:view-sequential-outline","tags":["view"]},{"name":"mdi:virtual-reality","tags":["vr"]},{"name":"mdi:virus","tags":["science","medical / hospital"]},{"name":"mdi:virus-off","tags":["science"]},{"name":"mdi:virus-off-outline","tags":["science"]},{"name":"mdi:virus-outline","tags":["science","medical / hospital"]},{"name":"mdi:volume-equal","tags":["audio"]},{"name":"mdi:volume-minus","tags":["audio","home automation","cellphone / phone","volume decrease"]},{"name":"mdi:volume-mute","tags":["audio","cellphone / phone"]},{"name":"mdi:volume-plus","tags":["audio","home automation","cellphone / phone","volume increase"]},{"name":"mdi:volume-variant-off","tags":["audio","cellphone / phone"]},{"name":"mdi:volume-vibrate","tags":["cellphone / phone","audio"]},{"name":"mdi:vpn","tags":["virtual private network"]},{"name":"mdi:wall","tags":["bricks"]},{"name":"mdi:wall-fire","tags":["device / tech","firewall"]},{"name":"mdi:wall-sconce","tags":["home automation"]},{"name":"mdi:wall-sconce-flat","tags":["home automation","ceiling light flat","pot light flat"]},{"name":"mdi:wall-sconce-flat-outline","tags":["home automation"]},{"name":"mdi:wall-sconce-flat-variant","tags":["home automation","pot light flat variant"]},{"name":"mdi:wall-sconce-flat-variant-outline","tags":["home automation"]},{"name":"mdi:wall-sconce-outline","tags":["home automation"]},{"name":"mdi:wall-sconce-round","tags":["home automation","pot light round"]},{"name":"mdi:wall-sconce-round-outline","tags":["home automation"]},{"name":"mdi:wall-sconce-round-variant","tags":["home automation","pot light round variant"]},{"name":"mdi:wall-sconce-round-variant-outline","tags":["home automation"]},{"name":"mdi:wallet-bifold","tags":["currency","banking"]},{"name":"mdi:wallet-bifold-outline","tags":["banking","currency"]},{"name":"mdi:wallet-plus","tags":["banking","wallet add"]},{"name":"mdi:wallet-plus-outline","tags":["banking","wallet add outline"]},{"name":"mdi:wan","tags":["wide area network"]},{"name":"mdi:wardrobe","tags":["home automation","closet"]},{"name":"mdi:wardrobe-outline","tags":["home automation","closet outline"]},{"name":"mdi:warehouse","tags":["places"]},{"name":"mdi:washing-machine-alert","tags":["home automation","alert / error","laundry room alert"]},{"name":"mdi:washing-machine-off","tags":["home automation","laundry room off"]},{"name":"mdi:watch-export","tags":["device / tech"]},{"name":"mdi:watch-export-variant","tags":["device / tech"]},{"name":"mdi:watch-import","tags":["device / tech"]},{"name":"mdi:watch-import-variant","tags":["device / tech"]},{"name":"mdi:watch-variant","tags":["device / tech"]},{"name":"mdi:watch-vibrate","tags":["device / tech"]},{"name":"mdi:watch-vibrate-off","tags":["device / tech"]},{"name":"mdi:water-alert","tags":["alert / error","agriculture","drop alert","blood alert","ink alert"]},{"name":"mdi:water-alert-outline","tags":["alert / error","agriculture","drop alert outline","blood alert outline","ink alert outline"]},{"name":"mdi:water-boiler","tags":["home automation","water heater","gas water boiler","electric water boiler","gas water heater","electric water heater"]},{"name":"mdi:water-boiler-alert","tags":["home automation","alert / error","water heater alert","water boiler error","water heater error"]},{"name":"mdi:water-boiler-auto","tags":["home automation","water heater auto"]},{"name":"mdi:water-boiler-off","tags":["home automation","water heater off"]},{"name":"mdi:water-check","tags":["drop check","blood check","ink check"]},{"name":"mdi:water-check-outline","tags":["drop check outline","blood check outline","ink check outline"]},{"name":"mdi:water-circle","tags":["home automation","drop circle","blood circle","ink circle"]},{"name":"mdi:water-minus","tags":["drop minus","blood minus","ink minus"]},{"name":"mdi:water-minus-outline","tags":["drop minus outline","blood minus outline","ink minus outline"]},{"name":"mdi:water-off-outline","tags":["drop off outline","blood off outline","trans fat off outline","ink off outline"]},{"name":"mdi:water-opacity","tags":["home automation","drawing / art","weather","water transparent","water saver","blood saver","blood transparent","oil saver","oil transparent","drop transparent","drop saver"]},{"name":"mdi:water-outline","tags":["home automation","weather","drop outline","blood outline","water drop outline","ink outline"]},{"name":"mdi:water-percent","tags":["weather","home automation","nature","humidity","ink percent"]},{"name":"mdi:water-percent-alert","tags":["alert / error","nature","humidity alert","ink percent alert"]},{"name":"mdi:water-plus","tags":["drop plus","blood plus","ink plus"]},{"name":"mdi:water-plus-outline","tags":["drop plus outline","blood plus outline","ink plus outline"]},{"name":"mdi:water-polo","tags":["sport"]},{"name":"mdi:water-pump","tags":["agriculture","home automation","tap","kitchen tap","faucet"]},{"name":"mdi:water-pump-off","tags":["agriculture","home automation","tap off","kitchen tap off","faucet off"]},{"name":"mdi:water-remove","tags":["drop remove","blood remove","ink remove"]},{"name":"mdi:water-remove-outline","tags":["drop remove outline","blood remove outline","ink remove outline"]},{"name":"mdi:water-sync","tags":["agriculture","water recycle","water reuse"]},{"name":"mdi:water-thermometer","tags":["weather","home automation","boil point","water temperature","dew point"]},{"name":"mdi:water-thermometer-outline","tags":["weather","home automation","dew point outline","water temperature outline","boil point outline"]},{"name":"mdi:water-well","tags":[]},{"name":"mdi:water-well-outline","tags":[]},{"name":"mdi:waterfall","tags":["home automation","nature"]},{"name":"mdi:watering-can","tags":["agriculture","watering pot"]},{"name":"mdi:watering-can-outline","tags":["agriculture","watering pot outline"]},{"name":"mdi:wave","tags":["transportation + water","water"]},{"name":"mdi:waveform","tags":["audio"]},{"name":"mdi:waves","tags":["weather","transportation + water","agriculture","ocean","lake","flood","water"]},{"name":"mdi:waves-arrow-left","tags":["nature","weather","tide in","water flow"]},{"name":"mdi:waves-arrow-right","tags":["nature","weather","tide out","water flow"]},{"name":"mdi:waves-arrow-up","tags":["nature","weather","water evaporation","humidity","sea level rise","ocean level rise","climate change"]},{"name":"mdi:weather-cloudy","tags":["weather","cloud","agriculture"]},{"name":"mdi:weather-cloudy-alert","tags":["weather","alert / error","cloud"]},{"name":"mdi:weather-cloudy-arrow-right","tags":["weather","cloud"]},{"name":"mdi:weather-cloudy-clock","tags":["weather","cloud","weather history","weather time","weather date"]},{"name":"mdi:weather-dust","tags":["weather","agriculture","dust storm","windy"]},{"name":"mdi:weather-fog","tags":["weather","agriculture","weather mist"]},{"name":"mdi:weather-hail","tags":["weather","agriculture"]},{"name":"mdi:weather-hazy","tags":["weather","agriculture"]},{"name":"mdi:weather-hurricane","tags":["weather","nature","agriculture","cyclone"]},{"name":"mdi:weather-hurricane-outline","tags":["weather","nature","agriculture","cyclone outline"]},{"name":"mdi:weather-lightning","tags":["weather","agriculture","weather storm","weather thunder","weather flash"]},{"name":"mdi:weather-lightning-rainy","tags":["weather","weather thunder rainy","weather storm"]},{"name":"mdi:weather-night","tags":["weather","holiday","moon and stars","night sky"]},{"name":"mdi:weather-night-partly-cloudy","tags":["weather","cloud"]},{"name":"mdi:weather-partly-cloudy","tags":["weather","cloud","weather partlycloudy"]},{"name":"mdi:weather-partly-lightning","tags":["weather"]},{"name":"mdi:weather-partly-rainy","tags":["weather"]},{"name":"mdi:weather-partly-snowy","tags":["weather"]},{"name":"mdi:weather-partly-snowy-rainy","tags":["weather"]},{"name":"mdi:weather-pouring","tags":["weather","agriculture","weather heavy rain"]},{"name":"mdi:weather-rainy","tags":["weather","agriculture","weather drizzle","weather spitting"]},{"name":"mdi:weather-snowy","tags":["weather"]},{"name":"mdi:weather-snowy-heavy","tags":["weather","flurries"]},{"name":"mdi:weather-snowy-rainy","tags":["weather","weather sleet"]},{"name":"mdi:weather-sunny","tags":["weather"]},{"name":"mdi:weather-sunny-alert","tags":["weather","alert / error","home automation","heat alert","heat advisory","sun advisory"]},{"name":"mdi:weather-sunny-off","tags":["weather"]},{"name":"mdi:weather-sunset","tags":["weather"]},{"name":"mdi:weather-sunset-down","tags":["weather"]},{"name":"mdi:weather-sunset-up","tags":["weather","sunrise"]},{"name":"mdi:weather-tornado","tags":["weather"]},{"name":"mdi:weather-windy","tags":["weather"]},{"name":"mdi:weather-windy-variant","tags":["weather"]},{"name":"mdi:web-box","tags":["geographic information system","language box","globe box","internet box"]},{"name":"mdi:web-cancel","tags":[]},{"name":"mdi:web-check","tags":[]},{"name":"mdi:web-clock","tags":["date / time"]},{"name":"mdi:web-minus","tags":[]},{"name":"mdi:web-off","tags":[]},{"name":"mdi:web-plus","tags":[]},{"name":"mdi:web-refresh","tags":[]},{"name":"mdi:web-remove","tags":[]},{"name":"mdi:web-sync","tags":[]},{"name":"mdi:webcam","tags":["video / movie","home automation","web camera"]},{"name":"mdi:webcam-off","tags":[]},{"name":"mdi:webhook","tags":[]},{"name":"mdi:weight","tags":[]},{"name":"mdi:weight-gram","tags":[]},{"name":"mdi:weight-kilogram","tags":["weight kg"]},{"name":"mdi:weight-lifter","tags":["sport","people / family","crossfit","gym","fitness center","human barbell"]},{"name":"mdi:weight-pound","tags":["weight lb"]},{"name":"mdi:wheel-barrow","tags":["hardware / tools"]},{"name":"mdi:wheelchair","tags":["medical / hospital","people / family","accessible","isa","international symbol of access"]},{"name":"mdi:wheelchair-accessibility","tags":["medical / hospital","accessible"]},{"name":"mdi:whistle","tags":["sport"]},{"name":"mdi:whistle-outline","tags":["sport"]},{"name":"mdi:wifi","tags":[]},{"name":"mdi:wifi-alert","tags":["alert / error"]},{"name":"mdi:wifi-arrow-down","tags":[]},{"name":"mdi:wifi-arrow-left","tags":[]},{"name":"mdi:wifi-arrow-left-right","tags":[]},{"name":"mdi:wifi-arrow-right","tags":[]},{"name":"mdi:wifi-arrow-up","tags":[]},{"name":"mdi:wifi-arrow-up-down","tags":[]},{"name":"mdi:wifi-cancel","tags":[]},{"name":"mdi:wifi-check","tags":[]},{"name":"mdi:wifi-cog","tags":["settings"]},{"name":"mdi:wifi-lock","tags":["lock"]},{"name":"mdi:wifi-lock-open","tags":["lock"]},{"name":"mdi:wifi-marker","tags":["navigation","wifi location"]},{"name":"mdi:wifi-minus","tags":[]},{"name":"mdi:wifi-off","tags":[]},{"name":"mdi:wifi-plus","tags":[]},{"name":"mdi:wifi-refresh","tags":[]},{"name":"mdi:wifi-remove","tags":[]},{"name":"mdi:wifi-settings","tags":["settings"]},{"name":"mdi:wifi-star","tags":["wifi favourite","network favourite","wifi favorite","network favorite"]},{"name":"mdi:wifi-strength-1","tags":[]},{"name":"mdi:wifi-strength-1-alert","tags":["alert / error","wifi strength 1 warning"]},{"name":"mdi:wifi-strength-1-lock","tags":["lock"]},{"name":"mdi:wifi-strength-1-lock-open","tags":["lock"]},{"name":"mdi:wifi-strength-2","tags":[]},{"name":"mdi:wifi-strength-2-alert","tags":["alert / error","wifi strength 2 warning"]},{"name":"mdi:wifi-strength-2-lock","tags":["lock"]},{"name":"mdi:wifi-strength-2-lock-open","tags":["lock"]},{"name":"mdi:wifi-strength-3","tags":[]},{"name":"mdi:wifi-strength-3-alert","tags":["alert / error","wifi strength 3 warning"]},{"name":"mdi:wifi-strength-3-lock","tags":["lock"]},{"name":"mdi:wifi-strength-3-lock-open","tags":["lock"]},{"name":"mdi:wifi-strength-4","tags":[]},{"name":"mdi:wifi-strength-4-alert","tags":["alert / error","wifi strength 4 warning"]},{"name":"mdi:wifi-strength-4-lock","tags":["lock"]},{"name":"mdi:wifi-strength-4-lock-open","tags":["lock"]},{"name":"mdi:wifi-strength-alert-outline","tags":["alert / error","wifi strength warning outline","wifi strength 0 alert","wifi strength 0 warning"]},{"name":"mdi:wifi-strength-lock-open-outline","tags":["lock"]},{"name":"mdi:wifi-strength-lock-outline","tags":["lock","wifi strength 0 lock"]},{"name":"mdi:wifi-strength-off","tags":[]},{"name":"mdi:wifi-strength-off-outline","tags":[]},{"name":"mdi:wifi-strength-outline","tags":["wifi strength 0"]},{"name":"mdi:wifi-sync","tags":[]},{"name":"mdi:wind-turbine-alert","tags":["home automation","alert / error","wind power alert","wind turbine warning"]},{"name":"mdi:wind-turbine-check","tags":["home automation","wind power check","wind turbine success","wind power success"]},{"name":"mdi:window-close","tags":["cancel","close"]},{"name":"mdi:window-closed","tags":["home automation"]},{"name":"mdi:window-closed-variant","tags":["home automation"]},{"name":"mdi:window-maximize","tags":[]},{"name":"mdi:window-minimize","tags":[]},{"name":"mdi:window-open","tags":["home automation"]},{"name":"mdi:window-open-variant","tags":["home automation"]},{"name":"mdi:window-restore","tags":[]},{"name":"mdi:window-shutter","tags":["home automation"]},{"name":"mdi:window-shutter-alert","tags":["home automation","alert / error"]},{"name":"mdi:window-shutter-auto","tags":["home automation"]},{"name":"mdi:window-shutter-cog","tags":["home automation","settings","window shutter settings"]},{"name":"mdi:window-shutter-open","tags":["home automation"]},{"name":"mdi:window-shutter-settings","tags":["home automation","settings"]},{"name":"mdi:windsock","tags":["weather"]},{"name":"mdi:wiper","tags":[]},{"name":"mdi:wiper-wash","tags":["automotive","wiper fluid","washer fluid"]},{"name":"mdi:wiper-wash-alert","tags":["alert / error","automotive","wiper fluid alert","washer fluid alert","wiper fluid low","washer fluid low"]},{"name":"mdi:wizard-hat","tags":["clothing","gaming / rpg"]},{"name":"mdi:wrap","tags":[]},{"name":"mdi:wrap-disabled","tags":["unwrap"]},{"name":"mdi:wrench-check","tags":[]},{"name":"mdi:wrench-check-outline","tags":[]},{"name":"mdi:wrench-clock","tags":["date / time","hardware / tools","scheduled maintenance","wrench time","tool time","tool clock"]},{"name":"mdi:wrench-clock-outline","tags":["date / time"]},{"name":"mdi:wrench-cog","tags":["settings","wrench settings"]},{"name":"mdi:wrench-cog-outline","tags":["settings","wrench settings outline"]},{"name":"mdi:xml","tags":["developer / languages","code"]},{"name":"mdi:yeast","tags":[]},{"name":"mdi:yin-yang","tags":["taoism"]},{"name":"mdi:yoga","tags":["sport"]},{"name":"mdi:yurt","tags":[]},{"name":"mdi:zip-box-outline","tags":["files / folders","compressed file outline"]},{"name":"mdi:zip-disk","tags":[]},{"name":"mdi:zodiac-aquarius","tags":["horoscope aquarius"]},{"name":"mdi:zodiac-aries","tags":["horoscope aries"]},{"name":"mdi:zodiac-cancer","tags":["horoscope cancer"]},{"name":"mdi:zodiac-capricorn","tags":["horoscope capricorn"]},{"name":"mdi:zodiac-gemini","tags":["horoscope gemini"]},{"name":"mdi:zodiac-leo","tags":["horoscope leo"]},{"name":"mdi:zodiac-libra","tags":["horoscope libra"]},{"name":"mdi:zodiac-pisces","tags":["horoscope pisces"]},{"name":"mdi:zodiac-sagittarius","tags":["horoscope sagittarius"]},{"name":"mdi:zodiac-scorpio","tags":["horoscope scorpio"]},{"name":"mdi:zodiac-taurus","tags":["horoscope taurus"]},{"name":"mdi:zodiac-virgo","tags":["horoscope virgo"]}] \ No newline at end of file diff --git a/ui-ngx/src/assets/widget/value-card/centered-layout.svg b/ui-ngx/src/assets/widget/value-card/centered-layout.svg new file mode 100644 index 0000000000..9c8d5f38ae --- /dev/null +++ b/ui-ngx/src/assets/widget/value-card/centered-layout.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/value-card/horizontal-layout.svg b/ui-ngx/src/assets/widget/value-card/horizontal-layout.svg new file mode 100644 index 0000000000..283ddd461d --- /dev/null +++ b/ui-ngx/src/assets/widget/value-card/horizontal-layout.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/value-card/horizontal-reversed-layout.svg b/ui-ngx/src/assets/widget/value-card/horizontal-reversed-layout.svg new file mode 100644 index 0000000000..275f45fab9 --- /dev/null +++ b/ui-ngx/src/assets/widget/value-card/horizontal-reversed-layout.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/value-card/simplified-layout.svg b/ui-ngx/src/assets/widget/value-card/simplified-layout.svg new file mode 100644 index 0000000000..9488bc78e5 --- /dev/null +++ b/ui-ngx/src/assets/widget/value-card/simplified-layout.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/value-card/square-layout.svg b/ui-ngx/src/assets/widget/value-card/square-layout.svg new file mode 100644 index 0000000000..87b087cdad --- /dev/null +++ b/ui-ngx/src/assets/widget/value-card/square-layout.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/value-card/vertical-layout.svg b/ui-ngx/src/assets/widget/value-card/vertical-layout.svg new file mode 100644 index 0000000000..e2baba8ce4 --- /dev/null +++ b/ui-ngx/src/assets/widget/value-card/vertical-layout.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 2753fc745d..4a4c018549 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -66,6 +66,7 @@ overflow: visible; } > .mat-expansion-panel-header { + user-select: none; font-weight: 500; font-size: 16px; line-height: 24px; @@ -138,6 +139,13 @@ padding: 7px 7px 7px 16px; border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 6px; + &.no-border { + border: none; + border-radius: 0; + } + &.no-padding { + padding: 0; + } &.same-padding { padding-right: 16px; } @@ -154,9 +162,18 @@ &.medium-width { width: 220px; } + @media #{$mat-xs} { + width: auto; + &.medium-width { + width: auto; + } + } } .fixed-title-width { min-width: 200px; + @media #{$mat-xs} { + min-width: 0; + } } .mat-slide:only-child { margin: 8px 0; @@ -193,7 +210,7 @@ } .mat-mdc-text-field-wrapper { &.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) { - &:not(.mdc-text-field--focused):not(.mdc-text-field--disabled):not(:hover) { + &:not(.mdc-text-field--focused):not(.mdc-text-field--disabled):not(.mdc-text-field--invalid):not(:hover) { .mdc-notched-outline__leading, .mdc-notched-outline__trailing { border-color: rgba(0, 0, 0, 0.12); } @@ -416,4 +433,27 @@ line-height: 16px; } } + + button.mat-mdc-button-base.tb-box-button { + width: 40px; + min-width: 40px; + height: 40px; + padding: 7px; + .mat-mdc-button-touch-target { + width: 40px; + height: 40px; + } + &:not(:disabled) { + color: rgba(0, 0, 0, 0.54); + } + &:disabled { + color: rgba(0, 0, 0, 0.12); + } + > .mat-icon { + width: 24px; + height: 24px; + font-size: 24px; + margin: 0; + } + } } diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 127d60e3e5..d8bbdf743d 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -624,7 +624,7 @@ mat-label { .mat-toolbar.mat-primary { button.mat-mdc-icon-button { - mat-icon { + .mat-icon { color: white; } } @@ -648,11 +648,11 @@ mat-label { mat-toolbar.mat-mdc-table-toolbar:not(.mat-primary), .mat-mdc-cell, .mat-expansion-panel-header { button.mat-mdc-icon-button { - mat-icon { + .mat-icon { color: rgba(0, 0, 0, .54); } &[disabled][disabled] { - mat-icon { + .mat-icon { color: rgba(0, 0, 0, .26); } } @@ -791,7 +791,7 @@ mat-label { &.mat-number-cell { text-align: end; } - mat-icon { + .mat-icon { color: rgba(0, 0, 0, .54); } } @@ -951,7 +951,7 @@ mat-label { padding: 0 6px; min-width: 88px; } - mat-icon { + .mat-icon { margin-right: 5px; } } @@ -1051,7 +1051,7 @@ mat-label { background: #ccc; opacity: .85; - mat-icon { + .mat-icon { color: #666; } } @@ -1094,7 +1094,17 @@ mat-label { box-shadow: none; border-radius: 4px; .tb-color-result { - border: 1px solid rgba(0, 0, 0, 0.12); + position: relative; + &:after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.12); + } } &.disabled { cursor: initial; @@ -1155,7 +1165,7 @@ mat-label { .tb-drag-handle { cursor: move; - mat-icon { + .mat-icon { pointer-events: none; } } From 03b49f1ddd57419a68b7cdd7ad86659a65db1dfc Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 21 Jul 2023 18:56:06 +0300 Subject: [PATCH 111/168] Clear code --- .../DeviceConnectivityController.java | 1 - .../server/controller/DeviceController.java | 1 - .../DeviceConnectivityControllerTest.java | 41 ------------------- .../controller/DeviceControllerTest.java | 1 + .../dao/device/DeviceConnectivityService.java | 1 - ...e-check-connectivity-dialog.component.html | 32 +++++++-------- 6 files changed, 17 insertions(+), 60 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java index bf745a2033..abd45e0ca3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java @@ -42,7 +42,6 @@ import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.net.URISyntaxException; -import java.util.Map; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 3eb6202aea..d73915b617 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -134,7 +134,6 @@ public class DeviceController extends BaseController { private final TbDeviceService tbDeviceService; - @ApiOperation(value = "Get Device (getDeviceById)", notes = "Fetch the Device object based on the provided Device Id. " + "If the user has the authority of 'TENANT_ADMIN', the server checks that the device is owned by the same tenant. " + diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index b138778025..9fd8990a40 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -15,84 +15,43 @@ */ package org.thingsboard.server.controller; -import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.mockito.AdditionalAnswers; import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; -import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.OtaPackageInfo; -import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; -import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; -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.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceCredentialsId; -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.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; -import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest; -import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; import org.thingsboard.server.dao.device.DeviceDao; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; -import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; -import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; -import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.DOCKER; diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 9ab5f7fde8..1c952bd549 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -84,6 +84,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; + @ContextConfiguration(classes = {DeviceControllerTest.Config.class}) @DaoSqlTest public class DeviceControllerTest extends AbstractControllerTest { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java index 83f35d5566..51643fa1d4 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java @@ -20,7 +20,6 @@ import org.thingsboard.server.common.data.Device; import java.io.IOException; import java.net.URISyntaxException; -import java.util.Map; public interface DeviceConnectivityService { diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html index a595487521..01d2330aa3 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html @@ -144,8 +144,8 @@
@@ -165,8 +165,8 @@
@@ -186,14 +186,14 @@
- + Docker @@ -202,8 +202,8 @@
@@ -228,8 +228,8 @@
@@ -249,14 +249,14 @@
- + Docker @@ -265,8 +265,8 @@
From 8b19b5d1695c58ea958fbadd43b59dadf278c41f Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 21 Jul 2023 18:56:52 +0300 Subject: [PATCH 112/168] added curl command for mqtts --- .../ThingsboardSecurityConfiguration.java | 5 +- .../DeviceConnectivityController.java | 1 - .../DeviceConnectivityControllerTest.java | 51 +++++----- .../DeviceСonnectivityServiceImpl.java | 99 +++++++++++-------- .../dao/util/DeviceConnectivityUtil.java | 16 ++- 5 files changed, 100 insertions(+), 72 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index 793670f0ab..56a687be21 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -78,6 +78,7 @@ public class ThingsboardSecurityConfiguration { public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**"; public static final String MAIL_OAUTH2_PROCESSING_ENTRY_POINT = "/api/admin/mail/oauth2/code"; + public static final String DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT = "/api/device-connectivity/mqtts/certificate/download"; @Autowired private ThingsboardErrorResponseHandler restAccessDeniedHandler; @@ -136,7 +137,8 @@ public class ThingsboardSecurityConfiguration { protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception { List pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS)); pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT, - PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT)); + PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT, + DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT)); SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT); JwtTokenAuthenticationProcessingFilter filter = new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtHeaderTokenExtractor, matcher); @@ -204,6 +206,7 @@ public class ThingsboardSecurityConfiguration { .antMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll() // Public login end-point .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point .antMatchers(MAIL_OAUTH2_PROCESSING_ENTRY_POINT).permitAll() // Mail oauth2 code processing url + .antMatchers(DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT).permitAll() // Mail oauth2 code processing url .antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points .and() .authorizeRequests() diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java index bf745a2033..c11efc05a4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java @@ -89,7 +89,6 @@ public class DeviceConnectivityController extends BaseController { } @ApiOperation(value = "Download mqtt ssl certificate using file path defined in device.connectivity properties (downloadMqttServerCertificate)", notes = "Download Mqtt server certificate." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/device-connectivity/{protocol}/certificate/download", method = RequestMethod.GET) @ResponseBody public ResponseEntity downloadMqttServerCertificate(@ApiParam(value = PROTOCOL_PARAM_DESCRIPTION) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index b138778025..05c14dbb8b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -217,17 +217,17 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + "-u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(mqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + - "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", - credentials.getCredentialsId())); - + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl http://localhost:80/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tmp/tb-server-chain.pem -h localhost -p 8883 " + + "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + " -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --volume pathToFile/tb-server-chain.pem:/tmp/tb-server-chain.pem " + - "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -o /tmp/tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"\"", credentials.getCredentialsId())); JsonNode linuxCoapCommands = commands.get(COAP); @@ -251,21 +251,20 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); assertThat(commands).hasSize(1); - JsonNode linuxMqttCommands = commands.get(MQTT); - assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + - "-u %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(linuxMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + - "-t %s -u %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - + JsonNode mqttCommands = commands.get(MQTT); + assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + + "-u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl http://localhost:80/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tmp/tb-server-chain.pem -h localhost -p 8883 " + + "-t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + " -p 1883 -t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --volume pathToFile/tb-server-chain.pem:/tmp/tb-server-chain.pem " + - "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"", + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -o /tmp/tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); } @@ -295,20 +294,20 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); assertThat(commands).hasSize(1); - JsonNode linuxMqttCommands = commands.get(MQTT); - assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + - "-i %s -u %s -P %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(linuxMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + - "-t %s -i %s -u %s -P %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + JsonNode mqttCommands = commands.get(MQTT); + assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + + "-i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl http://localhost:80/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tmp/tb-server-chain.pem -h localhost -p 8883 " + + "-t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + " -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --volume pathToFile/tb-server-chain.pem:/tmp/tb-server-chain.pem " + - "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -o /tmp/tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); } @@ -330,7 +329,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { JsonNode commands = doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); assertThat(commands).hasSize(1); - assertThat(commands.get(MQTT).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(MQTT).get(MQTTS).get(0).asText()).isEqualTo(CHECK_DOCUMENTATION); assertThat(commands.get(MQTT).get(DOCKER)).isNull(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java index 284115ffb2..e15056a2a6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.device; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; @@ -36,6 +37,9 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Optional; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -48,7 +52,6 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.LINUX; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.WINDOWS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCoapClientCommand; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCurlCommand; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getDockerMosquittoClientsPublishCommand; @@ -77,7 +80,6 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService log.trace("Executing findDevicePublishTelemetryCommands [{}]", deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); - String defaultHostname = new URI(baseUrl).getHost(); DeviceCredentials creds = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); DeviceTransportType transportType = deviceProfile.getTransportType(); @@ -85,11 +87,11 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService ObjectNode commands = JacksonUtil.newObjectNode(); switch (transportType) { case DEFAULT: - Optional.ofNullable(getHttpTransportPublishCommands(defaultHostname, creds)) + Optional.ofNullable(getHttpTransportPublishCommands(baseUrl, creds)) .ifPresent(v -> commands.set(HTTP, v)); - Optional.ofNullable(getMqttTransportPublishCommands(defaultHostname, creds)) + Optional.ofNullable(getMqttTransportPublishCommands(baseUrl, creds)) .ifPresent(v -> commands.set(MQTT, v)); - Optional.ofNullable(getCoapTransportPublishCommands(defaultHostname, creds)) + Optional.ofNullable(getCoapTransportPublishCommands(baseUrl, creds)) .ifPresent(v -> commands.set(COAP, v)); break; case MQTT: @@ -97,11 +99,11 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); String topicName = transportConfiguration.getDeviceTelemetryTopic(); - Optional.ofNullable(getMqttTransportPublishCommands(defaultHostname, topicName, creds)) + Optional.ofNullable(getMqttTransportPublishCommands(baseUrl, topicName, creds)) .ifPresent(v -> commands.set(MQTT, v)); break; case COAP: - Optional.ofNullable(getCoapTransportPublishCommands(defaultHostname, creds)) + Optional.ofNullable(getCoapTransportPublishCommands(baseUrl, creds)) .ifPresent(v -> commands.set(COAP, v)); break; default: @@ -122,7 +124,7 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService } } - private JsonNode getHttpTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) { + private JsonNode getHttpTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) throws URISyntaxException { ObjectNode httpCommands = JacksonUtil.newObjectNode(); Optional.ofNullable(getHttpPublishCommand(HTTP, defaultHostname, deviceCredentials)) .ifPresent(v -> httpCommands.put(HTTP, v)); @@ -131,34 +133,37 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService return httpCommands.isEmpty() ? null : httpCommands; } - private String getHttpPublishCommand(String protocol, String defaultHostname, DeviceCredentials deviceCredentials) { + private String getHttpPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { DeviceConnectivityInfo httpProps = deviceConnectivityConfiguration.getConnectivity().get(protocol); if (httpProps == null || !httpProps.getEnabled() || deviceCredentials.getCredentialsType() != DeviceCredentialsType.ACCESS_TOKEN) { return null; } - String hostName = httpProps.getHost().isEmpty() ? defaultHostname : httpProps.getHost(); + String hostName = httpProps.getHost().isEmpty() ? new URI(baseUrl).getHost() : httpProps.getHost(); String port = httpProps.getPort().isEmpty() ? "" : ":" + httpProps.getPort(); return getCurlCommand(protocol, hostName, port, deviceCredentials); } - private JsonNode getMqttTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) { - return getMqttTransportPublishCommands(defaultHostname, DEFAULT_DEVICE_TELEMETRY_TOPIC, deviceCredentials); + private JsonNode getMqttTransportPublishCommands(String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { + return getMqttTransportPublishCommands(baseUrl, DEFAULT_DEVICE_TELEMETRY_TOPIC, deviceCredentials); } - private JsonNode getMqttTransportPublishCommands(String defaultHostname, String topic, DeviceCredentials deviceCredentials) { + private JsonNode getMqttTransportPublishCommands(String baseUrl, String topic, DeviceCredentials deviceCredentials) throws URISyntaxException { ObjectNode mqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getMqttPublishCommand(MQTT, defaultHostname, topic, deviceCredentials)) + Optional.ofNullable(getMqttPublishCommand(baseUrl, topic, deviceCredentials)) .ifPresent(v -> mqttCommands.put(MQTT, v)); - Optional.ofNullable(getMqttPublishCommand(MQTTS, defaultHostname, topic, deviceCredentials)) - .ifPresent(v -> mqttCommands.put(MQTTS, v)); + List mqttsPublishCommand = getMqttsPublishCommand(baseUrl, topic, deviceCredentials); + if (mqttsPublishCommand != null){ + ArrayNode arrayNode = mqttCommands.putArray(MQTTS); + mqttsPublishCommand.forEach(arrayNode::add); + } ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getDockerMqttPublishCommand(MQTT, defaultHostname, topic, deviceCredentials)) + Optional.ofNullable(getDockerMqttPublishCommand(MQTT,baseUrl, topic, deviceCredentials)) .ifPresent(v -> dockerMqttCommands.put(MQTT, v)); - Optional.ofNullable(getDockerMqttPublishCommand(MQTTS, defaultHostname, topic, deviceCredentials)) + Optional.ofNullable(getDockerMqttPublishCommand(MQTTS, baseUrl, topic, deviceCredentials)) .ifPresent(v -> dockerMqttCommands.put(MQTTS, v)); if (!dockerMqttCommands.isEmpty()) { @@ -167,41 +172,62 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService return mqttCommands.isEmpty() ? null : mqttCommands; } - private String getMqttPublishCommand(String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { - if (MQTTS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { - return CHECK_DOCUMENTATION; - } - DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); + private String getMqttPublishCommand(String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(MQTT); if (properties == null || !properties.getEnabled()) { return null; } - String mqttHost = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); + String mqttHost = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); - return getMosquittoPubPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + return getMosquittoPubPublishCommand(MQTT, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); } - private String getDockerMqttPublishCommand(String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + private List getMqttsPublishCommand(String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { + String pubCommand; + if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + return List.of(CHECK_DOCUMENTATION); + } else { + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(MQTTS); + if (properties == null || !properties.getEnabled()) { + return null; + } + String mqttHost = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); + String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); + pubCommand = getMosquittoPubPublishCommand(MQTTS, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + } + + ArrayList commands = new ArrayList<>(); + if (pubCommand != null) { + commands.add("curl " + baseUrl + "/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); + commands.add(pubCommand); + return commands; + } + return null; + } + + + private String getDockerMqttPublishCommand(String protocol, String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); if (properties == null || !properties.getEnabled()) { return null; } - String mqttHost = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); + String mqttHost = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); - return getDockerMosquittoClientsPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + return getDockerMosquittoClientsPublishCommand(protocol, baseUrl, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); } - private JsonNode getCoapTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) { + private JsonNode getCoapTransportPublishCommands(String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { ObjectNode coapCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getCoapPublishCommand(LINUX, COAP, defaultHostname, deviceCredentials)) + Optional.ofNullable(getCoapPublishCommand(COAP, baseUrl, deviceCredentials)) .ifPresent(v -> coapCommands.put(COAP, v)); - Optional.ofNullable(getCoapPublishCommand(LINUX, COAPS, defaultHostname, deviceCredentials)) + Optional.ofNullable(getCoapPublishCommand(COAPS, baseUrl, deviceCredentials)) .ifPresent(v -> coapCommands.put(COAPS, v)); return coapCommands.isEmpty() ? null : coapCommands; } - private String getCoapPublishCommand(String os, String protocol, String defaultHostname, DeviceCredentials deviceCredentials) { + private String getCoapPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { if (COAPS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { return CHECK_DOCUMENTATION; } @@ -209,14 +235,9 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService if (properties == null || !properties.getEnabled()) { return null; } - String hostName = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); + String hostName = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); - switch (os) { - case LINUX: - return getCoapClientCommand(protocol, hostName, port, deviceCredentials); - default: - throw new IllegalArgumentException("Unsupported operating system: " + os); - } + return getCoapClientCommand(protocol, hostName, port, deviceCredentials); } } 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 72eac8bdea..e99df56e64 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 @@ -19,6 +19,9 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials; +import java.util.ArrayList; +import java.util.List; + public class DeviceConnectivityUtil { public static final String HTTP = "http"; @@ -42,7 +45,7 @@ public class DeviceConnectivityUtil { public static String getMosquittoPubPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { StringBuilder command = new StringBuilder("mosquitto_pub -d -q 1"); if (MQTTS.equals(protocol)) { - command.append(" --cafile pathToFile/" + MQTT_SSL_PEM_FILE_NAME); + command.append(" --cafile tmp/" + MQTT_SSL_PEM_FILE_NAME); } command.append(" -h ").append(host).append(port == null ? "" : " -p " + port); command.append(" -t ").append(deviceTelemetryTopic); @@ -75,12 +78,12 @@ public class DeviceConnectivityUtil { return command.toString(); } - public static String getDockerMosquittoClientsPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { - StringBuilder command = new StringBuilder("docker run"); + public static String getDockerMosquittoClientsPublishCommand(String protocol, String baseUrl, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + StringBuilder command = new StringBuilder("docker run -it --rm thingsboard/mosquitto-clients "); if (MQTTS.equals(protocol)) { - command.append(" --volume pathToFile/" + MQTT_SSL_PEM_FILE_NAME + ":/tmp/" + MQTT_SSL_PEM_FILE_NAME); + command.append("/bin/sh -c \"curl -o /tmp/tb-server-chain.pem ").append(baseUrl).append("/api/device-connectivity/mqtts/certificate/download && "); } - command.append(" -it --rm thingsboard/mosquitto-clients pub"); + command.append("pub"); if (MQTTS.equals(protocol)) { command.append(" --cafile tmp/" + MQTT_SSL_PEM_FILE_NAME); } @@ -112,6 +115,9 @@ public class DeviceConnectivityUtil { return null; } command.append(" -m " + JSON_EXAMPLE_PAYLOAD); + if (MQTTS.equals(protocol)) { + command.append("\""); + } return command.toString(); } From 6a3be7fbaa61093409cb65a92446e9992823f6f3 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 24 Jul 2023 10:55:11 +0300 Subject: [PATCH 113/168] UI: fixed show commands in mqtt --- .../server/dao/device/DeviceСonnectivityServiceImpl.java | 8 ++++++-- .../server/dao/util/DeviceConnectivityUtil.java | 3 --- .../device-check-connectivity-dialog.component.scss | 1 + .../device/device-check-connectivity-dialog.component.ts | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java index e15056a2a6..e7bfefabbd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java @@ -156,8 +156,12 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService .ifPresent(v -> mqttCommands.put(MQTT, v)); List mqttsPublishCommand = getMqttsPublishCommand(baseUrl, topic, deviceCredentials); if (mqttsPublishCommand != null){ - ArrayNode arrayNode = mqttCommands.putArray(MQTTS); - mqttsPublishCommand.forEach(arrayNode::add); + if (mqttsPublishCommand.size() > 1) { + ArrayNode arrayNode = mqttCommands.putArray(MQTTS); + mqttsPublishCommand.forEach(arrayNode::add); + } else { + mqttCommands.put(MQTTS, mqttsPublishCommand.get(0)); + } } ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); 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 e99df56e64..dad405b093 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 @@ -19,9 +19,6 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials; -import java.util.ArrayList; -import java.util.List; - public class DeviceConnectivityUtil { public static final String HTTP = "http"; diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss index 1a95da0a14..e50b46d9fc 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss @@ -123,6 +123,7 @@ margin: 0; background: #F3F6FA; border-color: #305680; + padding-right: 38px; } } button.clipboard-btn { diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts index f185d88c6a..07da1a43ed 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts @@ -171,7 +171,7 @@ export class DeviceCheckConnectivityDialogComponent extends if (Array.isArray(commands)) { const formatCommands: Array = []; commands.forEach(command => formatCommands.push(this.createMarkDownSingleCommand(command))); - return formatCommands.join('
\n'); + return formatCommands.join(`\n
\n\n`); } else { return this.createMarkDownSingleCommand(commands); } From ce6046844ac13dee2f01a8f376f49e5ef65bc76a Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 24 Jul 2023 12:16:19 +0300 Subject: [PATCH 114/168] UI: Hide edit button if entity not selected --- .../profile/asset-profile-autocomplete.component.html | 2 +- .../profile/device-profile-autocomplete.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/asset-profile-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/profile/asset-profile-autocomplete.component.html index 87ee2a7fa1..5244cf9101 100644 --- a/ui-ngx/src/app/modules/home/components/profile/asset-profile-autocomplete.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/asset-profile-autocomplete.component.html @@ -35,7 +35,7 @@ (click)="clear()"> close - -
+
+ + {{ 'widgets.value-card.icon' | translate }} + +
+ + + + + + + + +
+
+
+
widgets.value-card.value
+
+ + + +
widget-config.decimals-suffix
+
+ + + + +
+
+
+
+ + {{ 'widgets.value-card.date' | translate }} + +
+ + + + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts index 9cf918905c..762b26ac42 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component } from '@angular/core'; +import { ChangeDetectorRef, Component, Injector } from '@angular/core'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -28,8 +28,13 @@ import { import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component'; -import { isDefinedAndNotNull, isUndefined } from '@core/utils'; -import { getLabel, setLabel } from '@home/components/widget/config/widget-settings.models'; +import { formatValue, isDefinedAndNotNull, isUndefined } from '@core/utils'; +import { + DateFormatProcessor, + DateFormatSettings, + getLabel, + setLabel +} from '@home/components/widget/config/widget-settings.models'; import { valueCardDefaultSettings, ValueCardLayout, @@ -65,9 +70,14 @@ export class ValueCardBasicConfigComponent extends BasicWidgetConfigComponent { valueCardWidgetConfigForm: UntypedFormGroup; + valuePreviewFn = this._valuePreviewFn.bind(this); + + datePreviewFn = this._datePreviewFn.bind(this); + constructor(protected store: Store, protected widgetConfigComponent: WidgetConfigComponent, private cd: ChangeDetectorRef, + private $injector: Injector, private fb: UntypedFormBuilder) { super(store, widgetConfigComponent); } @@ -251,4 +261,16 @@ export class ValueCardBasicConfigComponent extends BasicWidgetConfigComponent { config.enableFullscreen = buttons.includes('fullscreen'); } + private _valuePreviewFn(): string { + const units: string = this.valueCardWidgetConfigForm.get('units').value; + const decimals: number = this.valueCardWidgetConfigForm.get('decimals').value; + return formatValue(22, decimals, units, true); + } + + private _datePreviewFn(): string { + const dateFormat: DateFormatSettings = this.valueCardWidgetConfigForm.get('dateFormat').value; + const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat); + processor.update(Date.now()); + return processor.formatted; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts index 222c031cfb..34f2ac464b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts @@ -16,6 +16,10 @@ import { isDefinedAndNotNull, isNumber, isNumeric, parseFunction } from '@core/utils'; import { DataKey, Datasource, DatasourceData } from '@shared/models/widget.models'; +import { Injector } from '@angular/core'; +import { DatePipe, formatDate } from '@angular/common'; +import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; +import { TranslateService } from '@ngx-translate/core'; export type ComponentStyle = {[klass: string]: any}; @@ -168,6 +172,104 @@ class FunctionColorProcessor extends ColorProcessor { } } +export interface DateFormatSettings { + format?: string; + lastUpdateAgo?: boolean; + custom?: boolean; +} + +export const simpleDateFormat = (format: string): DateFormatSettings => ({ + format, + lastUpdateAgo: false, + custom: false +}); + +export const lastUpdateAgoDateFormat = (): DateFormatSettings => ({ + format: null, + lastUpdateAgo: true, + custom: false +}); + +export const customDateFormat = (format: string): DateFormatSettings => ({ + format, + lastUpdateAgo: false, + custom: true +}); + +export const dateFormats = ['MMM dd yyyy HH:mm', 'dd MMM yyyy HH:mm', 'yyyy MMM dd HH:mm', + 'MM/dd/yyyy HH:mm', 'dd/MM/yyyy HH:mm', 'yyyy/MM/dd HH:mm:ss'] + .map(f => simpleDateFormat(f)).concat([lastUpdateAgoDateFormat(), customDateFormat('EEE, MMMM dd, yyyy')]); + +export const compareDateFormats = (df1: DateFormatSettings, df2: DateFormatSettings): boolean => { + if (df1 === df2) { + return true; + } else if (df1 && df2) { + if (df1.lastUpdateAgo && df2.lastUpdateAgo) { + return true; + } else if (df1.custom && df2.custom) { + return true; + } else if (!df1.lastUpdateAgo && !df2.lastUpdateAgo && !df1.custom && !df2.custom) { + return df1.format === df2.format; + } + } + return false; +}; + +export abstract class DateFormatProcessor { + + static fromSettings($injector: Injector, settings: DateFormatSettings): DateFormatProcessor { + if (settings.lastUpdateAgo) { + return new LastUpdateAgoDateFormatProcessor($injector, settings); + } else { + return new SimpleDateFormatProcessor($injector, settings); + } + } + + formatted = ''; + + protected constructor(protected $injector: Injector, + protected settings: DateFormatSettings) { + } + + abstract update(ts: string | number | Date): void; + +} + +export class SimpleDateFormatProcessor extends DateFormatProcessor { + + private datePipe: DatePipe; + + constructor(protected $injector: Injector, + protected settings: DateFormatSettings) { + super($injector, settings); + this.datePipe = $injector.get(DatePipe); + } + + update(ts: string| number | Date): void { + this.formatted = this.datePipe.transform(ts, this.settings.format); + } + +} + +export class LastUpdateAgoDateFormatProcessor extends DateFormatProcessor { + + private dateAgoPipe: DateAgoPipe; + private translate: TranslateService; + + constructor(protected $injector: Injector, + protected settings: DateFormatSettings) { + super($injector, settings); + this.dateAgoPipe = $injector.get(DateAgoPipe); + this.translate = $injector.get(TranslateService); + } + + update(ts: string| number | Date): void { + this.formatted = this.translate.instant('date.last-update-n-ago-text', + {agoText: this.dateAgoPipe.transform(ts, {applyAgo: true, short: true, textPart: true})}); + } + +} + export enum BackgroundType { image = 'image', imageUrl = 'imageUrl', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.html index e76ff0b36a..8c79c0e1e7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.html @@ -63,7 +63,7 @@
{{ label }}
-
{{ dateText }}
+
{{ dateFormat.formatted }}
{{ valueText }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts index 787888d1c8..f495581571 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts @@ -21,7 +21,7 @@ import { DatePipe } from '@angular/common'; import { backgroundStyle, ColorProcessor, - ComponentStyle, + ComponentStyle, DateFormatProcessor, getDataKey, getLabel, getSingleTsValue, @@ -62,7 +62,7 @@ export class ValueCardWidgetComponent implements OnInit { valueColor: ColorProcessor; showDate = true; - dateText = ''; + dateFormat: DateFormatProcessor; dateStyle: ComponentStyle = {}; dateColor: ColorProcessor; @@ -70,7 +70,6 @@ export class ValueCardWidgetComponent implements OnInit { overlayStyle: ComponentStyle = {}; private horizontal = false; - private dateFormat: string; private decimals = 0; private units = ''; @@ -110,7 +109,7 @@ export class ValueCardWidgetComponent implements OnInit { this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor); this.showDate = this.settings.showDate; - this.dateFormat = this.settings.dateFormat; + this.dateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.dateFormat); this.dateStyle = textStyle(this.settings.dateFont, '1.33', '0.25px'); this.dateColor = ColorProcessor.fromSettings(this.settings.dateColor); @@ -126,15 +125,16 @@ export class ValueCardWidgetComponent implements OnInit { public onDataUpdated() { const tsValue = getSingleTsValue(this.ctx.data); + let ts; let value; if (tsValue) { + ts = tsValue[0]; value = tsValue[1]; this.valueText = formatValue(value, this.decimals, this.units, true); - this.dateText = this.date.transform(tsValue[0], this.dateFormat); } else { this.valueText = 'N/A'; - this.dateText = ''; } + this.dateFormat.update(ts); this.iconColor.update(value); this.labelColor.update(value); this.valueColor.update(value); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts index 5a54264bd0..23d9a30329 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts @@ -19,8 +19,8 @@ import { BackgroundType, ColorSettings, constantColor, - cssUnit, - Font + cssUnit, DateFormatSettings, + Font, lastUpdateAgoDateFormat } from '@home/components/widget/config/widget-settings.models'; export enum ValueCardLayout { @@ -75,7 +75,7 @@ export interface ValueCardWidgetSettings { valueFont: Font; valueColor: ColorSettings; showDate: boolean; - dateFormat: string; + dateFormat: DateFormatSettings; dateFont: Font; dateColor: ColorSettings; background: BackgroundSettings; @@ -106,7 +106,7 @@ export const valueCardDefaultSettings = (horizontal: boolean): ValueCardWidgetSe }, valueColor: constantColor('rgba(0, 0, 0, 0.87)'), showDate: true, - dateFormat: 'yyyy-MM-dd HH:mm:ss', + dateFormat: lastUpdateAgoDateFormat(), dateFont: { family: 'Roboto', size: 12, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts index c20abd8d84..2118bde6df 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts @@ -108,6 +108,7 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit removeRange(index: number) { this.rangeListFormArray.removeAt(index); + this.colorSettingsFormGroup.markAsDirty(); setTimeout(() => {this.popover?.updatePosition();}, 0); } @@ -115,7 +116,8 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit const newRange: ColorRange = { color: 'rgba(0,0,0,0.87)' }; - this.rangeListFormArray.push(this.colorRangeControl(newRange), {emitEvent: true}); + this.rangeListFormArray.push(this.colorRangeControl(newRange)); + this.colorSettingsFormGroup.markAsDirty(); setTimeout(() => {this.popover?.updatePosition();}, 0); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.html new file mode 100644 index 0000000000..eaca1b6d40 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.html @@ -0,0 +1,22 @@ + + + + {{ cssUnit }} + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts new file mode 100644 index 0000000000..dc593e9564 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts @@ -0,0 +1,82 @@ +/// +/// 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. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms'; +import { cssUnit, cssUnits } from '@home/components/widget/config/widget-settings.models'; + +@Component({ + selector: 'tb-css-unit-select', + templateUrl: './css-unit-select.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => CssUnitSelectComponent), + multi: true + } + ] +}) +export class CssUnitSelectComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + cssUnitsList = cssUnits; + + cssUnitFormControl: UntypedFormControl; + + modelValue: cssUnit; + + private propagateChange = null; + + constructor() {} + + ngOnInit(): void { + this.cssUnitFormControl = new UntypedFormControl(); + this.cssUnitFormControl.valueChanges.subscribe((value: cssUnit) => { + this.updateModel(value); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.cssUnitFormControl.disable(); + } else { + this.cssUnitFormControl.enable(); + } + } + + writeValue(value: cssUnit): void { + this.modelValue = value; + this.cssUnitFormControl.patchValue(this.modelValue, {emitEvent: false}); + } + + updateModel(value: cssUnit): void { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.html new file mode 100644 index 0000000000..7310d0c403 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.html @@ -0,0 +1,26 @@ + + + + {{ dateFormatDisplayValue(dateFormat) }} + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts new file mode 100644 index 0000000000..413111ad1e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts @@ -0,0 +1,148 @@ +/// +/// 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. +/// + +import { Component, forwardRef, Input, OnInit, Renderer2, ViewChild, ViewContainerRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms'; +import { + compareDateFormats, + dateFormats, + DateFormatSettings +} from '@home/components/widget/config/widget-settings.models'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { deepClone } from '@core/utils'; +import { + DateFormatSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/date-format-settings-panel.component'; + +@Component({ + selector: 'tb-date-format-select', + templateUrl: './date-format-select.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DateFormatSelectComponent), + multi: true + } + ] +}) +export class DateFormatSelectComponent implements OnInit, ControlValueAccessor { + + @ViewChild('customFormatButton', {static: false}) + customFormatButton: MatButton; + + @Input() + disabled: boolean; + + dateFormatList = dateFormats; + + dateFormatsCompare = compareDateFormats; + + dateFormatFormControl: UntypedFormControl; + + modelValue: DateFormatSettings; + + private propagateChange = null; + + private formatCache: {[format: string]: string} = {}; + + constructor(private translate: TranslateService, + private date: DatePipe, + private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef) {} + + ngOnInit(): void { + this.dateFormatFormControl = new UntypedFormControl(); + this.dateFormatFormControl.valueChanges.subscribe((value: DateFormatSettings) => { + this.updateModel(value); + if (value?.custom) { + setTimeout(() => { + this.openDateFormatSettingsPopup(null, this.customFormatButton); + }, 0); + } + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.dateFormatFormControl.disable(); + } else { + this.dateFormatFormControl.enable(); + } + } + + writeValue(value: DateFormatSettings): void { + this.modelValue = value; + this.dateFormatFormControl.patchValue(this.modelValue, {emitEvent: false}); + } + + updateModel(value: DateFormatSettings): void { + if (!compareDateFormats(this.modelValue, value)) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + dateFormatDisplayValue(value: DateFormatSettings): string { + if (value.custom) { + return this.translate.instant('date.custom-date'); + } else if (value.lastUpdateAgo) { + return this.translate.instant('date.last-update-n-ago'); + } else { + if (!this.formatCache[value.format]) { + this.formatCache[value.format] = this.date.transform(Date.now(), value.format); + } + return this.formatCache[value.format]; + } + } + + openDateFormatSettingsPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + dateFormat: deepClone(this.modelValue) + }; + const dateFormatSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, DateFormatSettingsPanelComponent, 'top', true, null, + ctx, + {}, + {}, {}, true); + dateFormatSettingsPanelPopover.tbComponentRef.instance.popover = dateFormatSettingsPanelPopover; + dateFormatSettingsPanelPopover.tbComponentRef.instance.dateFormatApplied.subscribe((dateFormat) => { + dateFormatSettingsPanelPopover.hide(); + this.modelValue = dateFormat; + this.propagateChange(this.modelValue); + }); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.html new file mode 100644 index 0000000000..ee332924f0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.html @@ -0,0 +1,47 @@ + +
+
date.custom-date
+
+
date.format
+ + +
+
+
+ +
+
date.preview
+
{{ previewText }}
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.scss new file mode 100644 index 0000000000..ec6a762966 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.scss @@ -0,0 +1,67 @@ +/** + * 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. + */ + +@import '../../../../../../../../scss/constants'; + +.tb-date-format-settings-panel { + width: 500px; + display: flex; + flex-direction: column; + gap: 16px; + @media #{$mat-xs} { + width: 90vw; + } + .tb-date-format-settings-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-form-row { + .fixed-title-width { + min-width: 120px; + } + &.date-format-preview { + align-items: flex-start; + .preview-text { + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + letter-spacing: 0.2px; + color: rgba(0, 0, 0, 0.38); + } + } + .mat-mdc-form-field.tb-date-format-input { + .mat-mdc-text-field-wrapper.mdc-text-field--outlined { + .mat-mdc-form-field-icon-suffix { + display: flex; + align-items: center; + line-height: normal; + } + } + } + } + .tb-date-format-settings-panel-buttons { + height: 60px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.ts new file mode 100644 index 0000000000..47f54fd5d5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.ts @@ -0,0 +1,70 @@ +/// +/// 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. +/// + +import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { DateFormatSettings } from '@home/components/widget/config/widget-settings.models'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { UntypedFormControl, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DatePipe } from '@angular/common'; + +@Component({ + selector: 'tb-date-format-settings-panel', + templateUrl: './date-format-settings-panel.component.html', + providers: [], + styleUrls: ['./date-format-settings-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class DateFormatSettingsPanelComponent extends PageComponent implements OnInit { + + @Input() + dateFormat: DateFormatSettings; + + @Input() + popover: TbPopoverComponent; + + @Output() + dateFormatApplied = new EventEmitter(); + + dateFormatFormControl: UntypedFormControl; + + previewText = ''; + + constructor(private date: DatePipe, + protected store: Store) { + super(store); + } + + ngOnInit(): void { + this.dateFormatFormControl = new UntypedFormControl(this.dateFormat.format, [Validators.required]); + this.dateFormatFormControl.valueChanges.subscribe((value: string) => { + this.previewText = this.date.transform(Date.now(), value); + }); + this.previewText = this.date.transform(Date.now(), this.dateFormat.format); + } + + cancel() { + this.popover?.hide(); + } + + applyDateFormat() { + this.dateFormat.format = this.dateFormatFormControl.value; + this.dateFormatApplied.emit(this.dateFormat); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html index 8cf2e4e9e3..140c525ac2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html @@ -23,11 +23,7 @@ - - - {{ cssUnit }} - - +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts index 9369167746..91a71bc8d7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts @@ -28,7 +28,6 @@ import { PageComponent } from '@shared/components/page.component'; import { commonFonts, ComponentStyle, - cssUnits, Font, fontStyles, fontStyleTranslations, @@ -66,8 +65,6 @@ export class FontSettingsPanelComponent extends PageComponent implements OnInit @ViewChild('familyInput', {static: true}) familyInput: ElementRef; - cssUnitsList = cssUnits; - fontWeightsList = fontWeights; fontWeightTranslationsMap = fontWeightTranslations; 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 1a9834dd91..68b84578c1 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 @@ -276,6 +276,11 @@ import { ColorSettingsComponent } from '@home/components/widget/lib/settings/com import { ColorSettingsPanelComponent } from '@home/components/widget/lib/settings/common/color-settings-panel.component'; +import { CssUnitSelectComponent } from '@home/components/widget/lib/settings/common/css-unit-select.component'; +import { DateFormatSelectComponent } from '@home/components/widget/lib/settings/common/date-format-select.component'; +import { + DateFormatSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/date-format-settings-panel.component'; @NgModule({ declarations: [ @@ -383,7 +388,10 @@ import { FontSettingsComponent, FontSettingsPanelComponent, ColorSettingsComponent, - ColorSettingsPanelComponent + ColorSettingsPanelComponent, + CssUnitSelectComponent, + DateFormatSelectComponent, + DateFormatSettingsPanelComponent ], imports: [ CommonModule, @@ -495,7 +503,10 @@ import { FontSettingsComponent, FontSettingsPanelComponent, ColorSettingsComponent, - ColorSettingsPanelComponent + ColorSettingsPanelComponent, + CssUnitSelectComponent, + DateFormatSelectComponent, + DateFormatSettingsPanelComponent ] }) export class WidgetSettingsModule { diff --git a/ui-ngx/src/app/shared/components/help-markdown.component.ts b/ui-ngx/src/app/shared/components/help-markdown.component.ts index 97bd326b46..90ac325c55 100644 --- a/ui-ngx/src/app/shared/components/help-markdown.component.ts +++ b/ui-ngx/src/app/shared/components/help-markdown.component.ts @@ -24,6 +24,7 @@ import { import { BehaviorSubject } from 'rxjs'; import { share } from 'rxjs/operators'; import { HelpService } from '@core/services/help.service'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-help-markdown', @@ -36,7 +37,9 @@ export class HelpMarkdownComponent implements OnDestroy, OnInit, OnChanges { @Input() helpContent: string; - @Input() visible: boolean; + @Input() + @coerceBoolean() + visible: boolean; @Input() style: { [klass: string]: any } = {}; diff --git a/ui-ngx/src/app/shared/components/unit-input.component.html b/ui-ngx/src/app/shared/components/unit-input.component.html index 3686796dbf..d001a43ef2 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.html +++ b/ui-ngx/src/app/shared/components/unit-input.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + 0) { - let res = this.translate.instant(`timewindow.${i}`, {[i]: counter}); + let res = this.translate.instant(`timewindow.${i+(short ? '-short' : '')}`, {[i]: counter}); if (applyAgo) { res += ' ' + this.translate.instant('timewindow.ago'); } diff --git a/ui-ngx/src/assets/help/en_US/date/date-format.md b/ui-ngx/src/assets/help/en_US/date/date-format.md new file mode 100644 index 0000000000..502ab7d4f7 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/date/date-format.md @@ -0,0 +1,88 @@ +#### Pre-defined format options + +| Option | Equivalent to | Examples (given in `en-US` locale) | +|------------|---------------------------------|-----------------------------------------------| +| short | M/d/yy, h:mm a | 6/15/15, 9:03 AM | +| medium | MMM d, y, h:mm:ss a | Jun 15, 2015, 9:03:01 AM | +| long | MMMM d, y, h:mm:ss a z | June 15, 2015 at 9:03:01 AM GMT+1 | +| full | EEEE, MMMM d, y, h:mm:ss a zzzz | Monday, June 15, 2015 at 9:03:01 AM GMT+01:00 | +| shortDate | M/d/yy | 6/15/15 | +| mediumDate | MMM d, y | Jun 15, 2015 | +| longDate | MMMM d, y | June 15, 2015 | +| fullDate | EEEE, MMMM d, y | Monday, June 15, 2015 | +| shortTime | h:mm a | 9:03 AM | +| mediumTime | h:mm:ss a | 9:03:01 AM | +| longTime | h:mm:ss a z | 9:03:01 AM GMT+1 | +| fullTime | h:mm:ss a zzzz | 9:03:01 AM GMT+01:00 | + +#### Custom format options + +You can construct a format string using symbols to specify the components +of a date-time value, as described in the following table. +Format details depend on the locale. +Fields marked with (*) are only available in the extra data set for the given locale. + +| Field type | Format | Description | Example Value | +|---------------------|-------------|--------------------------------------------------------------|------------------------------------------------------------| +| Era | G, GG & GGG | Abbreviated | AD | +| | GGGG | Wide | Anno Domini | +| | GGGGG | Narrow | A | +| Year | y | Numeric: minimum digits | 2, 20, 201, 2017, 20173 | +| | yy | Numeric: 2 digits + zero padded | 02, 20, 01, 17, 73 | +| | yyy | Numeric: 3 digits + zero padded | 002, 020, 201, 2017, 20173 | +| | yyyy | Numeric: 4 digits or more + zero padded | 0002, 0020, 0201, 2017, 20173 | +| Week-numbering year | Y | Numeric: minimum digits | 2, 20, 201, 2017, 20173 | +| | YY | Numeric: 2 digits + zero padded | 02, 20, 01, 17, 73 | +| | YYY | Numeric: 3 digits + zero padded | 002, 020, 201, 2017, 20173 | +| | YYYY | Numeric: 4 digits or more + zero padded | 0002, 0020, 0201, 2017, 20173 | +| Month | M | Numeric: 1 digit | 9, 12 | +| | MM | Numeric: 2 digits + zero padded | 09, 12 | +| | MMM | Abbreviated | Sep | +| | MMMM | Wide | September | +| | MMMMM | Narrow | S | +| Month standalone | L | Numeric: 1 digit | 9, 12 | +| | LL | Numeric: 2 digits + zero padded | 09, 12 | +| | LLL | Abbreviated | Sep | +| | LLLL | Wide | September | +| | LLLLL | Narrow | S | +| Week of year | w | Numeric: minimum digits | 1... 53 | +| | ww | Numeric: 2 digits + zero padded | 01... 53 | +| Week of month | W | Numeric: 1 digit | 1... 5 | +| Day of month | d | Numeric: minimum digits | 1 | +| | dd | Numeric: 2 digits + zero padded | 01 | +| Week day | E, EE & EEE | Abbreviated | Tue | +| | EEEE | Wide | Tuesday | +| | EEEEE | Narrow | T | +| | EEEEEE | Short | Tu | +| Week day standalone | c, cc | Numeric: 1 digit | 2 | +| | ccc | Abbreviated | Tue | +| | cccc | Wide | Tuesday | +| | ccccc | Narrow | T | +| | cccccc | Short | Tu | +| Period | a, aa & aaa | Abbreviated | am/pm or AM/PM | +| | aaaa | Wide (fallback to `a` when missing) | ante meridiem/post meridiem | +| | aaaaa | Narrow | a/p | +| Period* | B, BB & BBB | Abbreviated | mid. | +| | BBBB | Wide | am, pm, midnight, noon, morning, afternoon, evening, night | +| | BBBBB | Narrow | md | +| Period standalone* | b, bb & bbb | Abbreviated | mid. | +| | bbbb | Wide | am, pm, midnight, noon, morning, afternoon, evening, night | +| | bbbbb | Narrow | md | +| Hour 1-12 | h | Numeric: minimum digits | 1, 12 | +| | hh | Numeric: 2 digits + zero padded | 01, 12 | +| Hour 0-23 | H | Numeric: minimum digits | 0, 23 | +| | HH | Numeric: 2 digits + zero padded | 00, 23 | +| Minute | m | Numeric: minimum digits | 8, 59 | +| | mm | Numeric: 2 digits + zero padded | 08, 59 | +| Second | s | Numeric: minimum digits | 0... 59 | +| | ss | Numeric: 2 digits + zero padded | 00... 59 | +| Fractional seconds | S | Numeric: 1 digit | 0... 9 | +| | SS | Numeric: 2 digits + zero padded | 00... 99 | +| | SSS | Numeric: 3 digits + zero padded (= milliseconds) | 000... 999 | +| Zone | z, zz & zzz | Short specific non location format (fallback to O) | GMT-8 | +| | zzzz | Long specific non location format (fallback to OOOO) | GMT-08:00 | +| | Z, ZZ & ZZZ | ISO8601 basic format | -0800 | +| | ZZZZ | Long localized GMT format | GMT-8:00 | +| | ZZZZZ | ISO8601 extended format + Z indicator for offset 0 (= XXXXX) | -08:00 | +| | O, OO & OOO | Short localized GMT format | GMT-8 | +| | OOOO | Long localized GMT format | GMT-08:00 | 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 1a93bf03db..291fe0cda0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -940,6 +940,13 @@ "edges": "Customer edge instances", "manage-edges": "Manage edges" }, + "date": { + "last-update-n-ago": "Last update N ago", + "last-update-n-ago-text": "Last update {{ agoText }}", + "custom-date": "Custom date", + "format": "Format", + "preview": "Preview" + }, "datetime": { "date-from": "Date from", "time-from": "Time from", @@ -3832,15 +3839,22 @@ "timewindow": { "timewindow": "Timewindow", "years": "{ years, plural, =1 { year } other {# years } }", + "years-short": "{{ years }}y", "months": "{ months, plural, =1 { month } other {# months } }", + "months-short": "{{ months }}M", "weeks": "{ weeks, plural, =1 { week } other {# weeks } }", + "weeks-short": "{{ weeks }}w", "days": "{ days, plural, =1 { day } other {# days } }", + "days-short": "{{ days }}d", "hours": "{ hours, plural, =0 { hour } =1 {1 hour } other {# hours } }", "hr": "{{ hr }} hr", + "hr-short": "{{ hr }}h", "minutes": "{ minutes, plural, =0 { minute } =1 {1 minute } other {# minutes } }", "min": "{{ min }} min", + "min-short": "{{ min }}m", "seconds": "{ seconds, plural, =0 { second } =1 {1 second } other {# seconds } }", "sec": "{{ sec }} sec", + "sec-short": "{{ sec }}s", "short": { "days": "{ days, plural, =1 {1 day } other {# days } }", "hours": "{ hours, plural, =1 {1 hour } other {# hours } }", @@ -3859,6 +3873,7 @@ "hide": "Hide", "interval": "Interval", "just-now": "Just now", + "just-now-lower": "just now", "ago": "ago" }, "unit": { @@ -4208,6 +4223,7 @@ "decimals": "Number of digits after floating point", "units-short": "Units", "decimals-short": "Decimals", + "decimals-suffix": "decimals", "timewindow": "Timewindow", "use-dashboard-timewindow": "Use dashboard timewindow", "use-widget-timewindow": "Use widget timewindow", @@ -5235,7 +5251,10 @@ "layout-simplified": "Simplified", "layout-horizontal": "Horizontal", "layout-horizontal-reversed": "Horizontal reversed", - "label": "Label" + "label": "Label", + "icon": "Icon", + "value": "Value", + "date": "Date" }, "table": { "common-table-settings": "Common Table Settings", From 3b8a9d94ecfffeb3813bcc75d203c568be4ab567 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Mon, 24 Jul 2023 22:57:52 +0200 Subject: [PATCH 118/168] Lwm2m transport - merge non-unique endpoints for models fetched from cache --- .../model/LwM2MModelConfigServiceImpl.java | 8 +- .../LwM2MModelConfigServiceImplTest.java | 73 +++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImplTest.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java index 302bd20c8b..eef9a53024 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java @@ -52,7 +52,7 @@ import java.util.stream.Collectors; public class LwM2MModelConfigServiceImpl implements LwM2MModelConfigService { @Autowired - private TbLwM2MModelConfigStore modelStore; + TbLwM2MModelConfigStore modelStore; @Autowired @Lazy @@ -67,14 +67,14 @@ public class LwM2MModelConfigServiceImpl implements LwM2MModelConfigService { @Autowired private LwM2MTelemetryLogService logService; - private ConcurrentMap currentModelConfigs; + ConcurrentMap currentModelConfigs; @AfterStartUp(order = AfterStartUp.BEFORE_TRANSPORT_SERVICE) - private void init() { + public void init() { List models = modelStore.getAll(); log.debug("Fetched model configs: {}", models); currentModelConfigs = models.stream() - .collect(Collectors.toConcurrentMap(LwM2MModelConfig::getEndpoint, m -> m)); + .collect(Collectors.toConcurrentMap(LwM2MModelConfig::getEndpoint, m -> m, (existing, replacement) -> existing)); } @Override diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImplTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImplTest.java new file mode 100644 index 0000000000..fc54ca9e0b --- /dev/null +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImplTest.java @@ -0,0 +1,73 @@ +/** + * 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.transport.lwm2m.server.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MModelConfigStore; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.mock; + +class LwM2MModelConfigServiceImplTest { + + LwM2MModelConfigServiceImpl service; + TbLwM2MModelConfigStore modelStore; + + @BeforeEach + void setUp() { + service = new LwM2MModelConfigServiceImpl(); + modelStore = mock(TbLwM2MModelConfigStore.class); + service.modelStore = modelStore; + } + + @Test + void testInitWithDuplicatedModels() { + LwM2MModelConfig config = new LwM2MModelConfig("urn:imei:951358811362976"); + List models = List.of(config, config); + willReturn(models).given(modelStore).getAll(); + service.init(); + assertThat(service.currentModelConfigs).containsExactlyEntriesOf(Map.of(config.getEndpoint(), config)); + } + + @Test + void testInitWithNonUniqueEndpoints() { + LwM2MModelConfig configAlfa = new LwM2MModelConfig("urn:imei:951358811362976"); + LwM2MModelConfig configBravo = new LwM2MModelConfig("urn:imei:151358811362976"); + LwM2MModelConfig configDelta = new LwM2MModelConfig("urn:imei:151358811362976"); + assertThat(configBravo.getEndpoint()).as("non-unique endpoints provided").isEqualTo(configDelta.getEndpoint()); + List models = List.of(configAlfa, configBravo, configDelta); + willReturn(models).given(modelStore).getAll(); + service.init(); + assertThat(service.currentModelConfigs).containsExactlyInAnyOrderEntriesOf(Map.of( + configAlfa.getEndpoint(), configAlfa, + configBravo.getEndpoint(), configBravo + )); + } + + @Test + void testInitWithEmptyModels() { + willReturn(Collections.emptyList()).given(modelStore).getAll(); + service.init(); + assertThat(service.currentModelConfigs).isEmpty(); + } + +} From 152e2200f017cb1ea13627c9d6212ab7ce0e0f6d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 25 Jul 2023 10:11:04 +0300 Subject: [PATCH 119/168] UI: Clear code after merge --- ui-ngx/src/app/modules/home/components/router-tabs.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/router-tabs.component.ts b/ui-ngx/src/app/modules/home/components/router-tabs.component.ts index 2cc6b0da81..c5ffb11908 100644 --- a/ui-ngx/src/app/modules/home/components/router-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/router-tabs.component.ts @@ -96,7 +96,6 @@ export class RouterTabsComponent extends PageComponent implements OnInit { type: 'link', name: tab.data?.breadcrumb?.label ?? '', icon: tab.data?.breadcrumb?.icon ?? '', - isMdiIcon: tab.data?.breadcrumb?.icon.startsWith('mdi:') ?? false, path: `${sectionPath}/${tab.path}` })); } else { From 83d525aa92e3a16903ca0067fc9b7795aa0a032d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 25 Jul 2023 10:35:14 +0300 Subject: [PATCH 120/168] UI: Clear code after merge --- ui-ngx/src/app/app.component.ts | 9 ++++----- ui-ngx/src/app/shared/models/icon.models.ts | 9 ++++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index 8f6ab35590..a3b4657120 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -30,7 +30,7 @@ import { combineLatest } from 'rxjs'; import { selectIsAuthenticated, selectIsUserLoaded } from '@core/auth/auth.selectors'; import { distinctUntilChanged, filter, map, skip } from 'rxjs/operators'; import { AuthService } from '@core/auth/auth.service'; -import { svgIcons } from '@shared/models/icon.models'; +import { svgIcons, svgIconsUrl } from '@shared/models/icon.models'; @Component({ selector: 'tb-root', @@ -65,10 +65,9 @@ export class AppComponent implements OnInit { ); } - this.matIconRegistry.addSvgIcon('windows', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/windows.svg')); - this.matIconRegistry.addSvgIcon('macos', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/macos.svg')); - this.matIconRegistry.addSvgIcon('linux', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/linux.svg')); - this.matIconRegistry.addSvgIcon('docker', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/docker.svg')); + for (const svgIcon of Object.keys(svgIconsUrl)) { + this.matIconRegistry.addSvgIcon(svgIcon, this.domSanitizer.bypassSecurityTrustResourceUrl(svgIcons[svgIcon])); + } this.storageService.testLocalStorage(); diff --git a/ui-ngx/src/app/shared/models/icon.models.ts b/ui-ngx/src/app/shared/models/icon.models.ts index 8d7d4f65bd..85c6617aa1 100644 --- a/ui-ngx/src/app/shared/models/icon.models.ts +++ b/ui-ngx/src/app/shared/models/icon.models.ts @@ -56,8 +56,15 @@ export const svgIcons: {[key: string]: string} = { '' }; +export const svgIconsUrl: { [key: string]: string } = { + windows: '/assets/windows.svg', + macos: '/assets/macos.svg', + linux: '/assets/linux.svg', + docker: '/assets/docker.svg' +}; + const svgIconNamespaces: string[] = ['mdi']; -const svgIconNames = Object.keys(svgIcons); +const svgIconNames = [...Object.keys(svgIcons), ...Object.keys(svgIconsUrl)]; export const splitIconName = (iconName: string): [string, string] => { if (!iconName) { From 08fb544b7fc29877373b2f135dacbc9ccb5fd2f9 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 25 Jul 2023 15:22:18 +0300 Subject: [PATCH 121/168] UI: Fixed alarm filter panel --- .../alarm/alarm-filter-config.component.html | 22 +++++++++++-------- .../alarm/alarm-filter-config.component.scss | 20 ++++++++++++++++- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html index c5ed4fe52a..cbbcc2ccb9 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html @@ -56,25 +56,28 @@
-
-
alarm.alarm-status-list
+
+
alarm.alarm-status-list
{{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }}
-
-
alarm.alarm-severity-list
+
+
alarm.alarm-severity-list
{{ alarmSeverityTranslationMap.get(alarmSeverityEnum[alarmSeverity]) | translate }}
-
-
alarm.alarm-type-list
- +
+
alarm.alarm-type-list
+ @@ -89,9 +92,10 @@
-
-
alarm.assignee
+
+
alarm.assignee
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss index 1c10e244b5..95f78f8fde 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss @@ -15,11 +15,24 @@ */ :host { display: block; - overflow: hidden; + overflow: scroll; max-width: 100%; .mdc-button { max-width: 100%; } + + .filters-row-mobile { + flex-direction: column; + align-items: start; + border: none; + padding: 0; + } + .filters-title-mobile { + font-size: 14px; + } + .filters-fields-width-mobile { + width: 100%; + } } :host ::ng-deep { @@ -32,4 +45,9 @@ text-overflow: ellipsis; } } + .mat-mdc-chip { + .mdc-evolution-chip__cell, .mat-mdc-chip-action, .mat-mdc-chip-action-label { + overflow: hidden; + } + } } From bab3eef8d73552d10be83012e48e12ec9fba6e00 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 26 Jul 2023 10:19:45 +0300 Subject: [PATCH 122/168] UI: Fix install command in device connectivity --- ui-ngx/src/app/app.component.ts | 2 +- .../device/device-check-connectivity-dialog.component.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index a3b4657120..67e2fd7b42 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -66,7 +66,7 @@ export class AppComponent implements OnInit { } for (const svgIcon of Object.keys(svgIconsUrl)) { - this.matIconRegistry.addSvgIcon(svgIcon, this.domSanitizer.bypassSecurityTrustResourceUrl(svgIcons[svgIcon])); + this.matIconRegistry.addSvgIcon(svgIcon, this.domSanitizer.bypassSecurityTrustResourceUrl(svgIconsUrl[svgIcon])); } this.storageService.testLocalStorage(); diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html index 11e1435119..0f9c6dc055 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html @@ -94,7 +94,7 @@
device.connectivity.install-necessary-client-tools
+ [data]='createMarkDownCommand("brew install curl")'>
device.connectivity.install-necessary-client-tools
+ [data]='createMarkDownCommand("sudo apt-get install curl")'>
downloadMqttServerCertificate(@ApiParam(value = PROTOCOL_PARAM_DESCRIPTION) - @PathVariable(PROTOCOL) String protocol) throws ThingsboardException, IOException { - String certificate = checkSslServerPemFile(protocol); + @PathVariable(PROTOCOL) String protocol) throws ThingsboardException, IOException { + checkParameter(PROTOCOL, protocol); + var pemCert = + checkNotNull(deviceConnectivityService.getPemCertFile(protocol), protocol + " pem cert file is not found!"); - ByteArrayResource cert = new ByteArrayResource(certificate.getBytes()); return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + MQTT_SSL_PEM_FILE_NAME) - .header("x-filename", MQTT_SSL_PEM_FILE_NAME) - .contentLength(cert.contentLength()) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + PEM_CERT_FILE_NAME) + .header("x-filename", PEM_CERT_FILE_NAME) + .contentLength(pemCert.contentLength()) .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(cert); + .body(pemCert); } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 5886e74ce4..86e7ec0ffe 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1004,7 +1004,7 @@ device: enabled: "${DEVICE_CONNECTIVITY_MQTTS_ENABLED:false}" host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:}" port: "${DEVICE_CONNECTIVITY_MQTTS_PORT:8883}" - ssl_server_pem_path: "${DEVICE_CONNECTIVITY_MQTTS_SERVER_CHAIN_PATH:}" + pem_cert_file: "${DEVICE_CONNECTIVITY_MQTT_SSL_PEM_CERT:mqttserver.pem}" coap: enabled: "${DEVICE_CONNECTIVITY_COAP_ENABLED:true}" host: "${DEVICE_CONNECTIVITY_COAP_HOST:}" diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java index 51643fa1d4..90355d885d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java @@ -16,14 +16,14 @@ package org.thingsboard.server.dao.device; import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.core.io.Resource; import org.thingsboard.server.common.data.Device; -import java.io.IOException; import java.net.URISyntaxException; public interface DeviceConnectivityService { JsonNode findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException; - String getSslServerChain(String protocol) throws IOException; + Resource getPemCertFile(String protocol); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java index 454c795f12..033aa4e0bc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java @@ -26,4 +26,9 @@ import java.util.Map; @Data public class DeviceConnectivityConfiguration { private Map connectivity; + + public boolean isEnabled(String protocol) { + var info = connectivity.get(protocol); + return info != null && info.isEnabled(); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java index fa5c61328b..b243be9995 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java @@ -19,8 +19,8 @@ import lombok.Data; @Data public class DeviceConnectivityInfo { - private Boolean enabled; + private boolean enabled; private String host; private String port; - private String sslServerPemPath; + private String pemCertFile; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java similarity index 64% rename from dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java rename to dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java index e7bfefabbd..32a582ba07 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java @@ -19,26 +19,25 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.FileUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.ResourceUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.dao.util.DeviceConnectivityUtil; -import java.io.File; -import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -49,17 +48,12 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.DOCKER; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.LINUX; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCoapClientCommand; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCurlCommand; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getDockerMosquittoClientsPublishCommand; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getMosquittoPubPublishCommand; @Service("DeviceConnectivityDaoService") @Slf4j -public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService { +public class DeviceConnectivityServiceImpl implements DeviceConnectivityService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_DEVICE_ID = "Incorrect deviceId "; @@ -113,12 +107,13 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService } @Override - public String getSslServerChain(String protocol) throws IOException { - String mqttSslPemPath = deviceConnectivityConfiguration.getConnectivity() + public Resource getPemCertFile(String protocol) { + String certFilePath = deviceConnectivityConfiguration.getConnectivity() .get(protocol) - .getSslServerPemPath(); - if (!mqttSslPemPath.isEmpty() && ResourceUtils.resourceExists(this, mqttSslPemPath)) { - return FileUtils.readFileToString(new File(mqttSslPemPath), StandardCharsets.UTF_8); + .getPemCertFile(); + + if (StringUtils.isNotBlank(certFilePath) && ResourceUtils.resourceExists(this, certFilePath)) { + return new ClassPathResource(certFilePath); } else { return null; } @@ -134,15 +129,15 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService } private String getHttpPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { - DeviceConnectivityInfo httpProps = deviceConnectivityConfiguration.getConnectivity().get(protocol); - if (httpProps == null || !httpProps.getEnabled() || + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); + if (properties == null || !properties.isEnabled() || deviceCredentials.getCredentialsType() != DeviceCredentialsType.ACCESS_TOKEN) { return null; } - String hostName = httpProps.getHost().isEmpty() ? new URI(baseUrl).getHost() : httpProps.getHost(); - String port = httpProps.getPort().isEmpty() ? "" : ":" + httpProps.getPort(); + String hostName = getHost(baseUrl, properties); + String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); - return getCurlCommand(protocol, hostName, port, deviceCredentials); + return DeviceConnectivityUtil.getHttpPublishCommand(protocol, hostName, port, deviceCredentials); } private JsonNode getMqttTransportPublishCommands(String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { @@ -152,23 +147,31 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService private JsonNode getMqttTransportPublishCommands(String baseUrl, String topic, DeviceCredentials deviceCredentials) throws URISyntaxException { ObjectNode mqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getMqttPublishCommand(baseUrl, topic, deviceCredentials)) - .ifPresent(v -> mqttCommands.put(MQTT, v)); - List mqttsPublishCommand = getMqttsPublishCommand(baseUrl, topic, deviceCredentials); - if (mqttsPublishCommand != null){ - if (mqttsPublishCommand.size() > 1) { + if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + mqttCommands.put(MQTTS, CHECK_DOCUMENTATION); + return mqttCommands; + } + + ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); + + if (deviceConnectivityConfiguration.isEnabled(MQTT)) { + Optional.ofNullable(getMqttPublishCommand(baseUrl, topic, deviceCredentials)). + ifPresent(v -> mqttCommands.put(MQTT, v)); + + Optional.ofNullable(getDockerMqttPublishCommand(MQTT, baseUrl, topic, deviceCredentials)) + .ifPresent(v -> dockerMqttCommands.put(MQTT, v)); + } + + if (deviceConnectivityConfiguration.isEnabled(MQTTS)) { + List mqttsPublishCommand = getMqttsPublishCommand(baseUrl, topic, deviceCredentials); + if (mqttsPublishCommand != null) { ArrayNode arrayNode = mqttCommands.putArray(MQTTS); mqttsPublishCommand.forEach(arrayNode::add); - } else { - mqttCommands.put(MQTTS, mqttsPublishCommand.get(0)); } - } - ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getDockerMqttPublishCommand(MQTT,baseUrl, topic, deviceCredentials)) - .ifPresent(v -> dockerMqttCommands.put(MQTT, v)); - Optional.ofNullable(getDockerMqttPublishCommand(MQTTS, baseUrl, topic, deviceCredentials)) - .ifPresent(v -> dockerMqttCommands.put(MQTTS, v)); + Optional.ofNullable(getDockerMqttPublishCommand(MQTTS, baseUrl, topic, deviceCredentials)) + .ifPresent(v -> dockerMqttCommands.put(MQTTS, v)); + } if (!dockerMqttCommands.isEmpty()) { mqttCommands.set(DOCKER, dockerMqttCommands); @@ -178,70 +181,81 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService private String getMqttPublishCommand(String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(MQTT); - if (properties == null || !properties.getEnabled()) { - return null; - } - String mqttHost = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); + String mqttHost = getHost(baseUrl, properties); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); - return getMosquittoPubPublishCommand(MQTT, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + return DeviceConnectivityUtil.getMqttPublishCommand(MQTT, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); } private List getMqttsPublishCommand(String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { - String pubCommand; - if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { - return List.of(CHECK_DOCUMENTATION); - } else { - DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(MQTTS); - if (properties == null || !properties.getEnabled()) { - return null; - } - String mqttHost = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); - String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); - pubCommand = getMosquittoPubPublishCommand(MQTTS, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); - } + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(MQTTS); + String mqttHost = getHost(baseUrl, properties); + String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); + String pubCommand = DeviceConnectivityUtil.getMqttPublishCommand(MQTTS, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); ArrayList commands = new ArrayList<>(); if (pubCommand != null) { - commands.add("curl " + baseUrl + "/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); + commands.add(DeviceConnectivityUtil.getCurlPemCertCommand(baseUrl, MQTTS)); commands.add(pubCommand); return commands; } return null; } - private String getDockerMqttPublishCommand(String protocol, String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); - if (properties == null || !properties.getEnabled()) { - return null; - } - String mqttHost = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); + String mqttHost = getHost(baseUrl, properties); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); - return getDockerMosquittoClientsPublishCommand(protocol, baseUrl, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + return DeviceConnectivityUtil.getDockerMqttPublishCommand(protocol, baseUrl, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); } private JsonNode getCoapTransportPublishCommands(String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { ObjectNode coapCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getCoapPublishCommand(COAP, baseUrl, deviceCredentials)) - .ifPresent(v -> coapCommands.put(COAP, v)); - Optional.ofNullable(getCoapPublishCommand(COAPS, baseUrl, deviceCredentials)) - .ifPresent(v -> coapCommands.put(COAPS, v)); + if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + coapCommands.put(COAPS, CHECK_DOCUMENTATION); + return coapCommands; + } + + ObjectNode dockerCoapCommands = JacksonUtil.newObjectNode(); + + if (deviceConnectivityConfiguration.isEnabled(COAP)) { + Optional.ofNullable(getCoapPublishCommand(COAP, baseUrl, deviceCredentials)) + .ifPresent(v -> coapCommands.put(COAP, v)); + + Optional.ofNullable(getDockerCoapPublishCommand(COAP, baseUrl, deviceCredentials)) + .ifPresent(v -> dockerCoapCommands.put(COAP, v)); + } + + if (deviceConnectivityConfiguration.isEnabled(COAPS)) { + Optional.ofNullable(getCoapPublishCommand(COAPS, baseUrl, deviceCredentials)) + .ifPresent(v -> coapCommands.put(COAPS, v)); + + Optional.ofNullable(getDockerCoapPublishCommand(COAPS, baseUrl, deviceCredentials)) + .ifPresent(v -> dockerCoapCommands.put(COAPS, v)); + } + + if (!dockerCoapCommands.isEmpty()) { + coapCommands.set(DOCKER, dockerCoapCommands); + } return coapCommands.isEmpty() ? null : coapCommands; } private String getCoapPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { - if (COAPS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { - return CHECK_DOCUMENTATION; - } DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); - if (properties == null || !properties.getEnabled()) { - return null; - } - String hostName = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); + String hostName = getHost(baseUrl, properties); String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); + return DeviceConnectivityUtil.getCoapPublishCommand(protocol, hostName, port, deviceCredentials); + } + + private String getDockerCoapPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); + String host = getHost(baseUrl, properties); + String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); + return DeviceConnectivityUtil.getDockerCoapPublishCommand(protocol, host, port, deviceCredentials); + } - return getCoapClientCommand(protocol, hostName, port, deviceCredentials); + private String getHost(String baseUrl, DeviceConnectivityInfo properties) throws URISyntaxException { + return properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); } } 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 dad405b093..1d20c62d70 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 @@ -30,19 +30,22 @@ public class DeviceConnectivityUtil { public static final String MQTTS = "mqtts"; public static final String COAP = "coap"; public static final String COAPS = "coaps"; - public static final String MQTT_SSL_PEM_FILE_NAME = "tb-server-chain.pem"; + public static final String PEM_CERT_FILE_NAME = "tb-server-chain.pem"; public static final String CHECK_DOCUMENTATION = "Check documentation"; public static final String JSON_EXAMPLE_PAYLOAD = "\"{temperature:25}\""; + public static final String DOCKER_RUN = "docker run --rm -it "; + public static final String MQTT_IMAGE = "thingsboard/mosquitto-clients "; + public static final String COAP_IMAGE = "thingsboard/coap-clients "; - public static String getCurlCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { + public static String getHttpPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { return String.format("curl -v -X POST %s://%s%s/api/v1/%s/telemetry --header Content-Type:application/json --data " + JSON_EXAMPLE_PAYLOAD, protocol, host, port, deviceCredentials.getCredentialsId()); } - public static String getMosquittoPubPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + public static String getMqttPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { StringBuilder command = new StringBuilder("mosquitto_pub -d -q 1"); if (MQTTS.equals(protocol)) { - command.append(" --cafile tmp/" + MQTT_SSL_PEM_FILE_NAME); + command.append(" --cafile ").append(PEM_CERT_FILE_NAME); } command.append(" -h ").append(host).append(port == null ? "" : " -p " + port); command.append(" -t ").append(deviceTelemetryTopic); @@ -75,50 +78,34 @@ public class DeviceConnectivityUtil { return command.toString(); } - public static String getDockerMosquittoClientsPublishCommand(String protocol, String baseUrl, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { - StringBuilder command = new StringBuilder("docker run -it --rm thingsboard/mosquitto-clients "); - if (MQTTS.equals(protocol)) { - command.append("/bin/sh -c \"curl -o /tmp/tb-server-chain.pem ").append(baseUrl).append("/api/device-connectivity/mqtts/certificate/download && "); - } - command.append("pub"); - if (MQTTS.equals(protocol)) { - command.append(" --cafile tmp/" + MQTT_SSL_PEM_FILE_NAME); - } - command.append(" -h ").append(host).append(port == null ? "" : " -p " + port); - command.append(" -t ").append(deviceTelemetryTopic); + public static String getDockerMqttPublishCommand(String protocol, String baseUrl, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + String mqttCommand = getMqttPublishCommand(protocol, host, port, deviceTelemetryTopic, deviceCredentials); - switch (deviceCredentials.getCredentialsType()) { - case ACCESS_TOKEN: - command.append(" -u ").append(deviceCredentials.getCredentialsId()); - break; - case MQTT_BASIC: - BasicMqttCredentials credentials = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), - BasicMqttCredentials.class); - if (credentials != null) { - if (credentials.getClientId() != null) { - command.append(" -i ").append(credentials.getClientId()); - } - if (credentials.getUserName() != null) { - command.append(" -u ").append(credentials.getUserName()); - } - if (credentials.getPassword() != null) { - command.append(" -P ").append(credentials.getPassword()); - } - } else { - return null; - } - break; - default: - return null; + if (mqttCommand == null) { + return null; } - command.append(" -m " + JSON_EXAMPLE_PAYLOAD); + + StringBuilder mqttDockerCommand = new StringBuilder(); + mqttDockerCommand.append(DOCKER_RUN).append(MQTT_IMAGE); + if (MQTTS.equals(protocol)) { - command.append("\""); + mqttDockerCommand.append("/bin/sh -c \"") + .append(getCurlPemCertCommand(baseUrl, protocol)) + .append(" && ") + .append(mqttCommand) + .append("\""); + } else { + mqttDockerCommand.append(mqttCommand); } - return command.toString(); + + return mqttDockerCommand.toString(); } - public static String getCoapClientCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { + public static String getCurlPemCertCommand(String baseUrl, String protocol) { + return String.format("curl -f -S -o %s %s/api/device-connectivity/%s/certificate/download", PEM_CERT_FILE_NAME, baseUrl, protocol); + } + + public static String getCoapPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { switch (deviceCredentials.getCredentialsType()) { case ACCESS_TOKEN: String client = COAPS.equals(protocol) ? "coap-client-openssl" : "coap-client"; @@ -128,4 +115,9 @@ public class DeviceConnectivityUtil { return null; } } + + public static String getDockerCoapPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { + String coapCommand = getCoapPublishCommand(protocol, host, port, deviceCredentials); + return coapCommand != null ? String.format("%s%s%s", DOCKER_RUN, COAP_IMAGE, coapCommand) : null; + } } From b8253b139b9c531b12bac74433665dc273ca88ab Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 26 Jul 2023 10:23:32 +0200 Subject: [PATCH 124/168] fixed tests --- .../DeviceConnectivityControllerTest.java | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index 5e40f3e993..7427ec1fc1 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -57,10 +57,8 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.DOCKER; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.LINUX; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.WINDOWS; @TestPropertySource(properties = { "device.connectivity.https.enabled=true", @@ -157,7 +155,8 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); DeviceCredentials credentials = doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); @@ -176,24 +175,24 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + "-u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl http://localhost:80/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); - assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tmp/tb-server-chain.pem -h localhost -p 8883 " + - "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl -f -S -o tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 " + + "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); - assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + " -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients " + - "/bin/sh -c \"curl -o /tmp/tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + - "pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"\"", + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -f -S -o tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"\"", credentials.getCredentialsId())); JsonNode linuxCoapCommands = commands.get(COAP); assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry " + - "-t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + "-t json -e \"{temperature:25}\"", credentials.getCredentialsId())); assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry" + - " -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + " -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } @Test @@ -207,23 +206,24 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); assertThat(commands).hasSize(1); JsonNode mqttCommands = commands.get(MQTT); assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + - "-u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl http://localhost:80/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); - assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tmp/tb-server-chain.pem -h localhost -p 8883 " + + "-u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl -f -S -o tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 " + "-t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); - assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + " -p 1883 -t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients " + - "/bin/sh -c \"curl -o /tmp/tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + - "pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"\"", + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -f -S -o tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); } @@ -250,23 +250,24 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); assertThat(commands).hasSize(1); JsonNode mqttCommands = commands.get(MQTT); assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + "-i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl http://localhost:80/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); - assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tmp/tb-server-chain.pem -h localhost -p 8883 " + + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl -f -S -o tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 " + "-t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); - assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + " -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients " + - "/bin/sh -c \"curl -o /tmp/tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + - "pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"\"", + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -f -S -o tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); } @@ -286,9 +287,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); assertThat(commands).hasSize(1); - assertThat(commands.get(MQTT).get(MQTTS).get(0).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(MQTT).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); assertThat(commands.get(MQTT).get(DOCKER)).isNull(); } @@ -303,7 +305,8 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); assertThat(commands).hasSize(1); JsonNode linuxCommands = commands.get(COAP); @@ -329,7 +332,8 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); assertThat(commands).hasSize(1); assertThat(commands.get(COAP).get(COAPS).asText()).isEqualTo(CHECK_DOCUMENTATION); } From 329a24c019cba7f2df062306d0b95ea3311d4f63 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 26 Jul 2023 11:05:50 +0200 Subject: [PATCH 125/168] added sparkplug --- .../DeviceConnectivityControllerTest.java | 4 ++-- .../dao/device/DeviceConnectivityServiceImpl.java | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index 7427ec1fc1..36a4365544 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -295,7 +295,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { } @Test - public void testFetchPublishTelemetryCommandsForСoapDevice() throws Exception { + public void testFetchPublishTelemetryCommandsForCoapDevice() throws Exception { Device device = new Device(); device.setName("My device"); device.setDeviceProfileId(coapDeviceProfileId); @@ -317,7 +317,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { } @Test - public void testFetchPublishTelemetryCommandsForСoapDeviceWithX509Creds() throws Exception { + public void testFetchPublishTelemetryCommandsForCoapDeviceWithX509Creds() throws Exception { Device device = new Device(); device.setName("My device"); device.setDeviceProfileId(coapDeviceProfileId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java index 32a582ba07..c06103d8f3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java @@ -91,10 +91,17 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService case MQTT: MqttDeviceProfileTransportConfiguration transportConfiguration = (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); - String topicName = transportConfiguration.getDeviceTelemetryTopic(); - - Optional.ofNullable(getMqttTransportPublishCommands(baseUrl, topicName, creds)) - .ifPresent(v -> commands.set(MQTT, v)); + //TODO: add sparkplug command with emulator (check SSL) + if (transportConfiguration.isSparkplug()) { + ObjectNode sparkplug = JacksonUtil.newObjectNode(); + sparkplug.put("sparkplug", CHECK_DOCUMENTATION); + commands.set(MQTT, sparkplug); + } else { + String topicName = transportConfiguration.getDeviceTelemetryTopic(); + + Optional.ofNullable(getMqttTransportPublishCommands(baseUrl, topicName, creds)) + .ifPresent(v -> commands.set(MQTT, v)); + } break; case COAP: Optional.ofNullable(getCoapTransportPublishCommands(baseUrl, creds)) From 5e83b2b903d9a9be0d281a55c13fbe18f0f43698 Mon Sep 17 00:00:00 2001 From: rusikv Date: Wed, 26 Jul 2023 14:26:00 +0300 Subject: [PATCH 126/168] Add double quotes to highlight 'remove other entities' confirm phrase in version control dialog --- ui-ngx/src/assets/locale/locale.constant-ca_ES.json | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- ui-ngx/src/assets/locale/locale.constant-es_ES.json | 2 +- ui-ngx/src/assets/locale/locale.constant-zh_CN.json | 2 +- ui-ngx/src/assets/locale/locale.constant-zh_TW.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json index 349d13da2c..dbb54c2bea 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json @@ -4481,7 +4481,7 @@ "created": "{{created}} creades", "updated": "{{updated}} actualitzades", "deleted": "{{deleted}} esborrades", - "remove-other-entities-confirm-text": "Atenció! Aquesta acció esborrarà permanentment todas les entitats actuals
no presents a la versió a restaurar.

Escriu eliminar altres entitats per confirmar.", + "remove-other-entities-confirm-text": "Atenció! Aquesta acció esborrarà permanentment todas les entitats actuals
no presents a la versió a restaurar.

Escriu \"remove other entities\" per confirmar.", "auto-commit-to-branch": "autopublicar a la branca {{ branch }}", "default-create-entity-version-name": "{{entityName}} actualizació", "sync-strategy-merge-hint": "Crea o actualitza les entitats seleccionades al repositori. Les altres entitats no seran modificades.", 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 0f96a5f1dc..094ff2ed52 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4429,7 +4429,7 @@ "created": "{{created}} created", "updated": "{{updated}} updated", "deleted": "{{deleted}} deleted", - "remove-other-entities-confirm-text": "Be careful! This will permanently delete all current entities
not present in the version you want to restore.

Please type remove other entities to confirm.", + "remove-other-entities-confirm-text": "Be careful! This will permanently delete all current entities
not present in the version you want to restore.

Please type \"remove other entities\" to confirm.", "auto-commit-to-branch": "auto-commit to {{ branch }} branch", "default-create-entity-version-name": "{{entityName}} update", "sync-strategy-merge-hint": "Creates or updates selected entities in the repository. All other repository entities are not modified.", diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index 6518e03f58..a2abda3b1e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -3907,7 +3907,7 @@ "created": "{{created}} creadas", "updated": "{{updated}} actualizadas", "deleted": "{{deleted}} borradas", - "remove-other-entities-confirm-text": "Atención! Esta acción borrará permanentemente todas las entidades actuales
no presentes en la versión a restaurar.

Escribe remove other entities para confirmar.", + "remove-other-entities-confirm-text": "Atención! Esta acción borrará permanentemente todas las entidades actuales
no presentes en la versión a restaurar.

Escribe \"remove other entities\" para confirmar.", "auto-commit-to-branch": "auto-publicar a la rama {{ branch }}", "default-create-entity-version-name": "{{entityName}} actualización", "sync-strategy-merge-hint": "Crea o actualiza las entidades seleccionadas en el repositorio. Las demás entidades no serán modificadas.", 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 b39e10e46c..401014aaca 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -3488,7 +3488,7 @@ "created": "{{created}} 创建", "updated": "{{updated}} 更新", "deleted": "{{deleted}} 删除", - "remove-other-entities-confirm-text": "请注意!在还原版本中不存在的当前实体
将被永久 删除。

请输入 remove other entities 进行确认。", + "remove-other-entities-confirm-text": "请注意!在还原版本中不存在的当前实体
将被永久 删除。

请输入 \"remove other entities\" 进行确认。", "auto-commit-to-branch": "自动提交到 {{ branch }} 分支", "default-create-entity-version-name": "{{entityName}} 更新", "sync-strategy-merge-hint": "创建或更新选定的实体,仓库其他实体均不修改。", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json index f2cce81824..0caea01b35 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json @@ -3338,7 +3338,7 @@ "created": "{{created}}已創建", "updated": "{{updated}}已更新", "deleted": "{{deleted}} 已刪除", - "remove-other-entities-confirm-text": "小心!這將永久刪除您要恢復的版本中不存在的所有當前實體。請鍵入刪除其他實體進行確認。", + "remove-other-entities-confirm-text": "小心!這將永久刪除所有在您要恢復的版本中不存在的當前實體。請輸入 \"remove other entities\" 進行確認。", "auto-commit-to-branch": "自動提交到{{ branch }}分支", "default-create-entity-version-name": "{{entityName}} 更新", "sync-strategy-merge-hint": "在存儲庫中創建或更新選定實體。所有其他存儲實體都不會被修改。", From 2d4fbd6833a65df07900f2249ad921abc77075a4 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 27 Jul 2023 14:48:53 +0300 Subject: [PATCH 127/168] added checkMsgType util method to TbMsg & resolved other review comments --- .../queue/DefaultTbClusterService.java | 4 +- .../state/DefaultDeviceStateService.java | 14 ++--- .../server/common/data/DataConstants.java | 44 +++++++++++++++ .../server/common/data/StringUtils.java | 6 +-- .../thingsboard/server/common/msg/TbMsg.java | 22 ++++++-- .../common/msg/session/SessionMsgType.java | 54 +++++++++++++++++++ .../engine/action/TbAbstractAlarmNode.java | 6 +-- .../TbCopyAttributesToEntityViewNode.java | 11 ++-- .../rule/engine/action/TbMsgCountNode.java | 2 +- .../rule/engine/aws/sns/TbSnsNode.java | 4 +- .../rule/engine/aws/sqs/TbSqsNode.java | 4 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 4 +- .../deduplication/TbMsgDeduplicationNode.java | 6 +-- .../rule/engine/delay/TbMsgDelayNode.java | 2 +- .../engine/edge/AbstractTbMsgPushNode.java | 51 +++++++----------- .../engine/filter/TbAssetTypeSwitchNode.java | 2 +- .../engine/filter/TbCheckRelationNode.java | 4 +- .../engine/filter/TbDeviceTypeSwitchNode.java | 2 +- .../rule/engine/gcp/pubsub/TbPubSubNode.java | 4 +- .../rule/engine/kafka/TbKafkaNode.java | 4 +- .../rule/engine/mail/TbSendEmailNode.java | 7 +-- .../rule/engine/math/TbMathNode.java | 2 +- .../engine/metadata/CalculateDeltaNode.java | 4 +- .../metadata/TbAbstractNodeWithFetchTo.java | 2 +- .../engine/metadata/TbGetTelemetryNode.java | 2 +- .../rule/engine/mqtt/TbMqttNode.java | 2 +- .../notification/TbNotificationNode.java | 3 +- .../rule/engine/profile/DeviceState.java | 31 +++++++---- .../engine/profile/TbDeviceProfileNode.java | 10 ++-- .../rule/engine/rabbitmq/TbRabbitMqNode.java | 2 +- .../rule/engine/rest/TbHttpClient.java | 4 +- .../rule/engine/rpc/TbSendRPCRequestNode.java | 4 +- .../engine/telemetry/TbMsgAttributesNode.java | 2 +- .../engine/telemetry/TbMsgTimeseriesNode.java | 2 +- 34 files changed, 217 insertions(+), 110 deletions(-) create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java 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 76631aaa95..9154c32701 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 @@ -223,9 +223,9 @@ public class DefaultTbClusterService implements TbClusterService { if (isRuleChainTransform && isQueueTransform) { tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName); } else if (isRuleChainTransform) { - tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId); + tbMsg = TbMsg.transformMsgRuleChainId(tbMsg, targetRuleChainId); } else if (isQueueTransform) { - tbMsg = TbMsg.transformMsg(tbMsg, targetQueueName); + tbMsg = TbMsg.transformMsgQueueName(tbMsg, targetQueueName); } return tbMsg; } 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 05d71c4fca..8b09565ef9 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 @@ -36,7 +36,6 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.ApiUsageRecordKey; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.EntityType; @@ -103,6 +102,9 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.DataConstants.SCOPE; +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; + /** * Created by ashvayka on 01.05.18. */ @@ -575,7 +577,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService> tsData = tsService.findLatest(TenantId.SYS_TENANT_ID, device.getId(), PERSISTENT_ATTRIBUTES); future = Futures.transform(tsData, extractDeviceStateData(device), deviceStateExecutor); } else { - ListenableFuture> attrData = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), DataConstants.SERVER_SCOPE, PERSISTENT_ATTRIBUTES); + ListenableFuture> attrData = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), SERVER_SCOPE, PERSISTENT_ATTRIBUTES); future = Futures.transform(attrData, extractDeviceStateData(device), deviceStateExecutor); } return transformInactivityTimeout(future); @@ -586,7 +588,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService { attributes.flatMap(KvEntry::getLongValue).ifPresent((inactivityTimeout) -> { if (inactivityTimeout > 0) { @@ -779,7 +781,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService(deviceId, key, value)); } else { - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); } } @@ -806,7 +808,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService(deviceId, key, value)); } else { - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index ed4431f445..02871a59b6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -54,9 +54,53 @@ public class DataConstants { return new String[]{CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE}; } + public static final String ALARM = "ALARM"; public static final String IN = "IN"; public static final String OUT = "OUT"; + public static final String INACTIVITY_EVENT = "INACTIVITY_EVENT"; + public static final String CONNECT_EVENT = "CONNECT_EVENT"; + public static final String DISCONNECT_EVENT = "DISCONNECT_EVENT"; + public static final String ACTIVITY_EVENT = "ACTIVITY_EVENT"; + + public static final String ENTITY_CREATED = "ENTITY_CREATED"; + public static final String ENTITY_UPDATED = "ENTITY_UPDATED"; + public static final String ENTITY_DELETED = "ENTITY_DELETED"; + public static final String ENTITY_ASSIGNED = "ENTITY_ASSIGNED"; + public static final String ENTITY_UNASSIGNED = "ENTITY_UNASSIGNED"; + public static final String ATTRIBUTES_UPDATED = "ATTRIBUTES_UPDATED"; + public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED"; + public static final String TIMESERIES_UPDATED = "TIMESERIES_UPDATED"; + public static final String TIMESERIES_DELETED = "TIMESERIES_DELETED"; + public static final String ALARM_ACK = "ALARM_ACK"; + public static final String ALARM_CLEAR = "ALARM_CLEAR"; + public static final String ALARM_ASSIGNED = "ALARM_ASSIGNED"; + public static final String ALARM_UNASSIGNED = "ALARM_UNASSIGNED"; + public static final String ALARM_DELETE = "ALARM_DELETE"; + public static final String COMMENT_CREATED = "COMMENT_CREATED"; + public static final String COMMENT_UPDATED = "COMMENT_UPDATED"; + public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT"; + public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT"; + public static final String PROVISION_SUCCESS = "PROVISION_SUCCESS"; + public static final String PROVISION_FAILURE = "PROVISION_FAILURE"; + public static final String ENTITY_ASSIGNED_TO_EDGE = "ENTITY_ASSIGNED_TO_EDGE"; + public static final String ENTITY_UNASSIGNED_FROM_EDGE = "ENTITY_UNASSIGNED_FROM_EDGE"; + + public static final String RELATION_ADD_OR_UPDATE = "RELATION_ADD_OR_UPDATE"; + public static final String RELATION_DELETED = "RELATION_DELETED"; + public static final String RELATIONS_DELETED = "RELATIONS_DELETED"; + + public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE"; + + public static final String RPC_QUEUED = "RPC_QUEUED"; + public static final String RPC_SENT = "RPC_SENT"; + public static final String RPC_DELIVERED = "RPC_DELIVERED"; + public static final String RPC_SUCCESSFUL = "RPC_SUCCESSFUL"; + public static final String RPC_TIMEOUT = "RPC_TIMEOUT"; + public static final String RPC_EXPIRED = "RPC_EXPIRED"; + public static final String RPC_FAILED = "RPC_FAILED"; + public static final String RPC_DELETED = "RPC_DELETED"; + public static final String DEFAULT_SECRET_KEY = ""; public static final String SECRET_KEY_FIELD_NAME = "secretKey"; public static final String DURATION_MS_FIELD_NAME = "durationMs"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java index 1e818ac8ef..a7671f4327 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java @@ -38,15 +38,15 @@ public class StringUtils { } public static boolean isBlank(String source) { - return isEmpty(source) || source.trim().isEmpty(); + return source == null || source.isEmpty() || source.trim().isEmpty(); } public static boolean isNotEmpty(String source) { - return !isEmpty(source); + return source != null && !source.isEmpty(); } public static boolean isNotBlank(String source) { - return !isBlank(source); + return source != null && !source.isEmpty() && !source.trim().isEmpty(); } public static String notBlankOrDefault(String src, String def) { 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 125260def5..bec094b804 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 @@ -279,7 +279,7 @@ public final class TbMsg implements Serializable { data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } - public static TbMsg transformMsg(TbMsg tbMsg, TbMsgMetaData metadata) { + public static TbMsg transformMsgMetadata(TbMsg tbMsg, TbMsgMetaData metadata) { return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, metadata.copy(), tbMsg.dataType, tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } @@ -289,17 +289,17 @@ public final class TbMsg implements Serializable { data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } - public static TbMsg transformMsg(TbMsg tbMsg, CustomerId customerId) { + public static TbMsg transformMsgCustomerId(TbMsg tbMsg, CustomerId customerId) { return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } - public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId) { + public static TbMsg transformMsgRuleChainId(TbMsg tbMsg, RuleChainId ruleChainId) { return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, ruleChainId, null, tbMsg.ctx.copy(), tbMsg.getCallback()); } - public static TbMsg transformMsg(TbMsg tbMsg, String queueName) { + public static TbMsg transformMsgQueueName(TbMsg tbMsg, String queueName) { return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, tbMsg.getRuleChainId(), null, tbMsg.ctx.copy(), tbMsg.getCallback()); } @@ -467,4 +467,18 @@ public final class TbMsg implements Serializable { } return ts; } + + public boolean checkType(TbMsgType tbMsgType) { + return tbMsgType != null && tbMsgType.name().equals(this.type); + } + + public boolean checkTypeOneOf(TbMsgType... types) { + for (TbMsgType type : types) { + if (checkType(type)) { + return true; + } + } + return false; + } + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java new file mode 100644 index 0000000000..ca7c94e9ce --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java @@ -0,0 +1,54 @@ +/** + * 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.common.msg.session; + +/** + * @deprecated This enum is deprecated and will be removed in a future version. + * Note: This enum was originally part of the public API but is now specific to CoAP transport only. + * Please use {@link org.thingsboard.server.transport.coap.CoapSessionMsgType} instead. + */ +@Deprecated(since="3.5.2", forRemoval = true) +public enum SessionMsgType { + GET_ATTRIBUTES_REQUEST(true), POST_ATTRIBUTES_REQUEST(true), GET_ATTRIBUTES_RESPONSE, + SUBSCRIBE_ATTRIBUTES_REQUEST, UNSUBSCRIBE_ATTRIBUTES_REQUEST, ATTRIBUTES_UPDATE_NOTIFICATION, + + POST_TELEMETRY_REQUEST(true), STATUS_CODE_RESPONSE, + + SUBSCRIBE_RPC_COMMANDS_REQUEST, UNSUBSCRIBE_RPC_COMMANDS_REQUEST, + TO_DEVICE_RPC_REQUEST, TO_DEVICE_RPC_RESPONSE, TO_DEVICE_RPC_RESPONSE_ACK, + + TO_SERVER_RPC_REQUEST(true), TO_SERVER_RPC_RESPONSE, + + RULE_ENGINE_ERROR, + + SESSION_OPEN, SESSION_CLOSE, + + CLAIM_REQUEST(); + + private final boolean requiresRulesProcessing; + + SessionMsgType() { + this(false); + } + + SessionMsgType(boolean requiresRulesProcessing) { + this.requiresRulesProcessing = requiresRulesProcessing; + } + + public boolean requiresRulesProcessing() { + return requiresRulesProcessing; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index 18317481bd..57871c91ff 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -24,10 +24,10 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.server.common.data.msg.TbMsgType; -import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -79,7 +79,7 @@ public abstract class TbAbstractAlarmNode> entityViewsFuture = @@ -94,7 +91,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { long startTime = entityView.getStartTimeMs(); long endTime = entityView.getEndTimeMs(); if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { - if (ATTRIBUTES_DELETED.name().equals(msg.getType())) { + if (msg.checkType(ATTRIBUTES_DELETED)) { List attributes = new ArrayList<>(); for (JsonElement element : JsonParser.parseString(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray()) { if (element.isJsonPrimitive()) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index 0fd99651a1..e43caf6bb8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -65,7 +65,7 @@ public class TbMsgCountNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.getType().equals(TbMsgType.MSG_COUNT_SELF_MSG.name()) && msg.getId().equals(nextTickId)) { + if (msg.checkType(TbMsgType.MSG_COUNT_SELF_MSG) && msg.getId().equals(nextTickId)) { JsonObject telemetryJson = new JsonObject(); telemetryJson.addProperty(this.telemetryPrefix + "_" + ctx.getServiceId(), messagesProcessed.longValue()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java index 00d84d3b5f..e24c73f9d3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java @@ -105,13 +105,13 @@ public class TbSnsNode extends TbAbstractExternalNode { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(MESSAGE_ID, result.getMessageId()); metaData.putValue(REQUEST_ID, result.getSdkResponseMetadata().getRequestId()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java index d99827f466..34072fbb8a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java @@ -134,13 +134,13 @@ public class TbSqsNode extends TbAbstractExternalNode { if (!StringUtils.isEmpty(result.getSequenceNumber())) { metaData.putValue(SEQUENCE_NUMBER, result.getSequenceNumber()); } - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private TbMsg processException(TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index febb2c1067..cd32a44ea0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -107,7 +107,7 @@ public class TbMsgGeneratorNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { log.trace("onMsg, config {}, msg {}", config, msg); - if (initialized.get() && msg.getType().equals(TbMsgType.GENERATOR_NODE_SELF_MSG.name()) && msg.getId().equals(nextTickId)) { + if (initialized.get() && msg.checkType(TbMsgType.GENERATOR_NODE_SELF_MSG) && msg.getId().equals(nextTickId)) { TbStopWatch sw = TbStopWatch.create(); withCallback(generate(ctx, msg), m -> { @@ -146,7 +146,7 @@ public class TbMsgGeneratorNode implements TbNode { private ListenableFuture generate(TbContext ctx, TbMsg msg) { log.trace("generate, config {}", config); if (prevMsg == null) { - prevMsg = ctx.newMsg(config.getQueueName(), "", originatorId, msg.getCustomerId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); + prevMsg = ctx.newMsg(config.getQueueName(), TbMsg.EMPTY_STRING, originatorId, msg.getCustomerId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); } if (initialized.get()) { ctx.logJsEvalRequest(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index fae40ff5f5..1c0803770b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -24,10 +24,10 @@ 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.msg.TbMsgType; -import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; +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.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; @@ -80,7 +80,7 @@ public class TbMsgDeduplicationNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - if (TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG.name().equals(msg.getType())) { + if (msg.checkType(TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG)) { processDeduplication(ctx, msg.getOriginator()); } else { processOnRegularMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index dabba5970a..d17415c1a6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -61,7 +61,7 @@ public class TbMsgDelayNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.getType().equals(TbMsgType.DELAY_TIMEOUT_SELF_MSG.name())) { + if (msg.checkType(TbMsgType.DELAY_TIMEOUT_SELF_MSG)) { TbMsg pendingMsg = pendingMsgs.remove(UUID.fromString(msg.getData())); if (pendingMsg != null) { ctx.enqueueForTellNext( 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 bbec000077..3472ea011b 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 @@ -67,7 +67,7 @@ public abstract class AbstractTbMsgPushNode metadata = msg.getMetaData().getData(); - EdgeEventActionType actionType = getEdgeEventActionTypeByMsgType(msgType, metadata); + EdgeEventActionType actionType = getEdgeEventActionTypeByMsgType(msg); Map entityBody = new HashMap<>(); JsonNode dataJson = JacksonUtil.toJsonNode(msg.getData()); switch (actionType) { @@ -158,45 +157,31 @@ public abstract class AbstractTbMsgPushNode metadata) { + protected EdgeEventActionType getEdgeEventActionTypeByMsgType(TbMsg msg) { EdgeEventActionType actionType; - if (POST_TELEMETRY_REQUEST.name().equals(msgType) - || TIMESERIES_UPDATED.name().equals(msgType)) { + if (msg.checkTypeOneOf(POST_TELEMETRY_REQUEST, TIMESERIES_UPDATED)) { actionType = EdgeEventActionType.TIMESERIES_UPDATED; - } else if (ATTRIBUTES_UPDATED.name().equals(msgType)) { + } else if (msg.checkType(ATTRIBUTES_UPDATED)) { actionType = EdgeEventActionType.ATTRIBUTES_UPDATED; - } else if (POST_ATTRIBUTES_REQUEST.name().equals(msgType)) { + } else if (msg.checkType(POST_ATTRIBUTES_REQUEST)) { actionType = EdgeEventActionType.POST_ATTRIBUTES; - } else if (ATTRIBUTES_DELETED.name().equals(msgType)) { + } else if (msg.checkType(ATTRIBUTES_DELETED)) { actionType = EdgeEventActionType.ATTRIBUTES_DELETED; - } else if (CONNECT_EVENT.name().equals(msgType) - || DISCONNECT_EVENT.name().equals(msgType) - || ACTIVITY_EVENT.name().equals(msgType) - || INACTIVITY_EVENT.name().equals(msgType)) { - String scope = metadata.get(SCOPE); - if ( StringUtils.isEmpty(scope)) { - actionType = EdgeEventActionType.TIMESERIES_UPDATED; - } else { - actionType = EdgeEventActionType.ATTRIBUTES_UPDATED; - } + } else if (msg.checkTypeOneOf(CONNECT_EVENT, DISCONNECT_EVENT, ACTIVITY_EVENT, INACTIVITY_EVENT)) { + String scope = msg.getMetaData().getValue(SCOPE); + actionType = StringUtils.isEmpty(scope) ? + EdgeEventActionType.TIMESERIES_UPDATED : EdgeEventActionType.ATTRIBUTES_UPDATED; } else { - log.warn("Unsupported msg type [{}]", msgType); - throw new IllegalArgumentException("Unsupported msg type: " + msgType); + String type = msg.getType(); + log.warn("Unsupported msg type [{}]", type); + throw new IllegalArgumentException("Unsupported msg type: " + type); } return actionType; } - protected boolean isSupportedMsgType(String msgType) { - return POST_TELEMETRY_REQUEST.name().equals(msgType) - || POST_ATTRIBUTES_REQUEST.name().equals(msgType) - || ATTRIBUTES_UPDATED.name().equals(msgType) - || ATTRIBUTES_DELETED.name().equals(msgType) - || TIMESERIES_UPDATED.name().equals(msgType) - || ALARM.name().equals(msgType) - || CONNECT_EVENT.name().equals(msgType) - || DISCONNECT_EVENT.name().equals(msgType) - || ACTIVITY_EVENT.name().equals(msgType) - || INACTIVITY_EVENT.name().equals(msgType); + protected boolean isSupportedMsgType(TbMsg msg) { + return msg.checkTypeOneOf(POST_TELEMETRY_REQUEST, POST_ATTRIBUTES_REQUEST, ATTRIBUTES_UPDATED, + ATTRIBUTES_DELETED, TIMESERIES_UPDATED, ALARM, CONNECT_EVENT, DISCONNECT_EVENT, ACTIVITY_EVENT, INACTIVITY_EVENT); } protected boolean isSupportedOriginator(EntityType entityType) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java index d70dd75040..39c37f5c81 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; configClazz = EmptyNodeConfiguration.class, nodeDescription = "Route incoming messages based on the name of the asset profile", nodeDetails = "Route incoming messages based on the name of the asset profile. The asset profile name is case-sensitive.

" + - "Output connections: Message originator profile name or Failure", + "Output connections: Asset profile name or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbAssetTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index 632c1af04f..3af5c9bfe1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -118,11 +118,11 @@ public class TbCheckRelationNode implements TbVersionedNode { throw new TbNodeException("property to update: '" + DIRECTION_PROPERTY_NAME + "' doesn't exists in configuration!"); } String direction = newConfigObjectNode.get(DIRECTION_PROPERTY_NAME).asText(); - if ("TO".equals(direction)) { + if (EntitySearchDirection.TO.name().equals(direction)) { newConfigObjectNode.put(DIRECTION_PROPERTY_NAME, EntitySearchDirection.FROM.name()); return new TbPair<>(true, newConfigObjectNode); } - if ("FROM".equals(direction)) { + if (EntitySearchDirection.FROM.name().equals(direction)) { newConfigObjectNode.put(DIRECTION_PROPERTY_NAME, EntitySearchDirection.TO.name()); return new TbPair<>(true, newConfigObjectNode); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java index 7765a4089d..b146a9be44 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; configClazz = EmptyNodeConfiguration.class, nodeDescription = "Route incoming messages based on the name of the device profile", nodeDetails = "Route incoming messages based on the name of the device profile. The device profile name is case-sensitive

" + - "Output connections: Message originator profile name or Failure", + "Output connections: Device profile name or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbDeviceTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java index e7dacbab4e..c55113783c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java @@ -119,13 +119,13 @@ public class TbPubSubNode extends TbAbstractExternalNode { private TbMsg processPublishResult(TbMsg origMsg, String messageId) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(MESSAGE_ID, messageId); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private TbMsg processException(TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private Publisher initPubSubClient() throws IOException { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 456aac4de1..68094196d0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -176,13 +176,13 @@ public class TbKafkaNode extends TbAbstractExternalNode { metaData.putValue(OFFSET, String.valueOf(recordMetadata.offset())); metaData.putValue(PARTITION, String.valueOf(recordMetadata.partition())); metaData.putValue(TOPIC, recordMetadata.topic()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private TbMsg processException(TbMsg origMsg, Exception e) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java index 78b7b23d5b..6319145637 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java @@ -70,7 +70,7 @@ public class TbSendEmailNode extends TbAbstractExternalNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { try { - validateType(msg.getType()); + validateType(msg); TbEmail email = getEmail(msg); var tbMsg = ackIfNeeded(ctx, msg); withCallback(ctx.getMailExecutor().executeAsync(() -> { @@ -100,8 +100,9 @@ public class TbSendEmailNode extends TbAbstractExternalNode { return email; } - private void validateType(String type) { - if (!TbMsgType.SEND_EMAIL.name().equals(type)) { + private void validateType(TbMsg msg) { + if (!msg.checkType(TbMsgType.SEND_EMAIL)) { + String type = msg.getType(); log.warn("Not expected msg type [{}] for SendEmail Node", type); throw new IllegalStateException("Not expected msg type " + type + " for SendEmail Node"); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java index 33692decc2..fe6deebbcf 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java @@ -248,7 +248,7 @@ public class TbMathNode implements TbNode { } else { md.putValue(mathResultKey, Double.toString(toDoubleValue(mathResultDef, result))); } - return TbMsg.transformMsg(msg, md); + return TbMsg.transformMsgMetadata(msg, md); } private double calculateResult(List args) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index 3e4e6eb93f..13fe3fae69 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -25,12 +25,12 @@ 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.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.TsKvEntry; 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.dao.timeseries.TimeseriesService; @@ -75,7 +75,7 @@ public class CalculateDeltaNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals(TbMsgType.POST_TELEMETRY_REQUEST.name())) { + if (!msg.checkType(TbMsgType.POST_TELEMETRY_REQUEST)) { ctx.tellNext(msg, TbNodeConnectionType.OTHER); return; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java index 2370bfde29..89e48b5f11 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java @@ -83,7 +83,7 @@ public abstract class TbAbstractNodeWithFetchTo> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(interval, keys)); DonAsynchron.withCallback(list, data -> { var metaData = updateMetadata(data, msg, keys); - ctx.tellSuccess(TbMsg.transformMsg(msg, metaData)); + ctx.tellSuccess(TbMsg.transformMsgMetadata(msg, metaData)); }, error -> ctx.tellFailure(msg, error), ctx.getDbCallbackExecutor()); } catch (Exception e) { ctx.tellFailure(msg, e); 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 d166f86008..23d2132a1b 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 @@ -95,7 +95,7 @@ public class TbMqttNode extends TbAbstractExternalNode { private TbMsg processException(TbMsg origMsg, Throwable e) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java index f1fd9727fa..5f1bea2bdb 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java @@ -19,7 +19,6 @@ import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.JacksonUtil; 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; @@ -77,7 +76,7 @@ public class TbNotificationNode extends TbAbstractExternalNode { ctx.getNotificationCenter().processNotificationRequest(ctx.getTenantId(), notificationRequest, stats -> { TbMsgMetaData metaData = tbMsg.getMetaData().copy(); metaData.putValue("notificationRequestResult", JacksonUtil.toString(stats)); - tellSuccess(ctx, TbMsg.transformMsg(tbMsg, metaData)); + tellSuccess(ctx, TbMsg.transformMsgMetadata(tbMsg, metaData)); })), r -> { }, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index 8cccc51258..7e0b0bc77c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -36,7 +36,6 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.rule.RuleNodeState; @@ -55,6 +54,18 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_ACK; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_CLEAR; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UNASSIGNED; +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; + @Slf4j class DeviceState { @@ -136,24 +147,24 @@ class DeviceState { latestValues = fetchLatestValues(ctx, deviceId); } boolean stateChanged = false; - if (msg.getType().equals(TbMsgType.POST_TELEMETRY_REQUEST.name())) { + if (msg.checkType(POST_TELEMETRY_REQUEST)) { stateChanged = processTelemetry(ctx, msg); - } else if (msg.getType().equals(TbMsgType.POST_ATTRIBUTES_REQUEST.name())) { + } else if (msg.checkType(POST_ATTRIBUTES_REQUEST)) { stateChanged = processAttributesUpdateRequest(ctx, msg); - } else if (msg.getType().equals(TbMsgType.ACTIVITY_EVENT.name()) || msg.getType().equals(TbMsgType.INACTIVITY_EVENT.name())) { + } else if (msg.checkTypeOneOf(ACTIVITY_EVENT, INACTIVITY_EVENT)) { stateChanged = processDeviceActivityEvent(ctx, msg); - } else if (msg.getType().equals(TbMsgType.ATTRIBUTES_UPDATED.name())) { + } else if (msg.checkType(ATTRIBUTES_UPDATED)) { stateChanged = processAttributesUpdateNotification(ctx, msg); - } else if (msg.getType().equals(TbMsgType.ATTRIBUTES_DELETED.name())) { + } else if (msg.checkType(ATTRIBUTES_DELETED)) { stateChanged = processAttributesDeleteNotification(ctx, msg); - } else if (msg.getType().equals(TbMsgType.ALARM_CLEAR.name())) { + } else if (msg.checkType(ALARM_CLEAR)) { stateChanged = processAlarmClearNotification(ctx, msg); - } else if (msg.getType().equals(TbMsgType.ALARM_ACK.name())) { + } else if (msg.checkType(ALARM_ACK)) { processAlarmAckNotification(ctx, msg); - } else if (msg.getType().equals(TbMsgType.ALARM_DELETE.name())) { + } else if (msg.checkType(ALARM_DELETE)) { processAlarmDeleteNotification(ctx, msg); } else { - if (msg.getType().equals(TbMsgType.ENTITY_ASSIGNED.name()) || msg.getType().equals(TbMsgType.ENTITY_UNASSIGNED.name())) { + if (msg.checkTypeOneOf(ENTITY_ASSIGNED, ENTITY_UNASSIGNED)) { dynamicPredicateValueCtx.resetCustomer(); } ctx.tellSuccess(msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 2de73b6f38..058a70fdea 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -108,16 +108,16 @@ public class TbDeviceProfileNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { EntityType originatorType = msg.getOriginator().getEntityType(); - if (msg.getType().equals(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG.name())) { + if (msg.checkType(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG)) { scheduleAlarmHarvesting(ctx, msg); harvestAlarms(ctx, System.currentTimeMillis()); return; } - if (msg.getType().equals(TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG.name())) { + if (msg.checkType(TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG)) { updateProfile(ctx, new DeviceProfileId(UUID.fromString(msg.getData()))); return; } - if (msg.getType().equals(TbMsgType.DEVICE_UPDATE_SELF_MSG.name())) { + if (msg.checkType(TbMsgType.DEVICE_UPDATE_SELF_MSG)) { JsonNode data = JacksonUtil.toJsonNode(msg.getData()); DeviceId deviceId = new DeviceId(UUID.fromString(data.get("deviceId").asText())); if (data.has("profileId")) { @@ -129,12 +129,12 @@ public class TbDeviceProfileNode implements TbNode { } if (EntityType.DEVICE.equals(originatorType)) { DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); - if (msg.getType().equals(TbMsgType.ENTITY_UPDATED.name())) { + if (msg.checkType(TbMsgType.ENTITY_UPDATED)) { invalidateDeviceProfileCache(deviceId, msg.getData()); ctx.tellSuccess(msg); return; } - if (msg.getType().equals(TbMsgType.ENTITY_DELETED.name())) { + if (msg.checkType(TbMsgType.ENTITY_DELETED)) { removeDeviceState(deviceId); ctx.tellSuccess(msg); return; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java index e83fefa513..be7a22f713 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java @@ -118,7 +118,7 @@ public class TbRabbitMqNode extends TbAbstractExternalNode { private TbMsg processException(TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 70a22a692e..0c9eca5f0a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -286,7 +286,7 @@ public class TbHttpClient { metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); metaData.putValue(ERROR_BODY, response.getBody()); headersToMetaData(response.getHeaders(), metaData::putValue); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private TbMsg processException(TbMsg origMsg, Throwable e) { @@ -298,7 +298,7 @@ public class TbHttpClient { metaData.putValue(STATUS_CODE, restClientResponseException.getRawStatusCode() + ""); metaData.putValue(ERROR_BODY, restClientResponseException.getResponseBodyAsString()); } - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private HttpHeaders prepareHeaders(TbMsg msg) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java index 26d22b5ef2..5859cb6f48 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -27,13 +27,13 @@ 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.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; 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; @@ -76,7 +76,7 @@ public class TbSendRPCRequestNode implements TbNode { ctx.tellFailure(msg, new RuntimeException("Params are not present in the message!")); } else { int requestId = json.has("requestId") ? json.get("requestId").getAsInt() : random.nextInt(); - boolean restApiCall = msg.getType().equals(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.name()); + boolean restApiCall = msg.checkType(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE); tmp = msg.getMetaData().getValue("oneway"); boolean oneway = !StringUtils.isEmpty(tmp) && Boolean.parseBoolean(tmp); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 5ddb1c701f..c7aa99c115 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -65,7 +65,7 @@ public class TbMsgAttributesNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals(POST_ATTRIBUTES_REQUEST.name())) { + if (!msg.checkType(POST_ATTRIBUTES_REQUEST)) { ctx.tellFailure(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType())); return; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java index 4118d28c22..852b74f64f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java @@ -82,7 +82,7 @@ public class TbMsgTimeseriesNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals(POST_TELEMETRY_REQUEST.name())) { + if (!msg.checkType(POST_TELEMETRY_REQUEST)) { ctx.tellFailure(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType())); return; } From b95eae215a34fd5a900c5120416ebc3210938745 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 27 Jul 2023 15:26:40 +0300 Subject: [PATCH 128/168] replaced handling of AssetProfile and DeviceProfile with HasRuleEngineProfile to match PE code version --- .../queue/DefaultTbClusterService.java | 66 ++++++++----------- 1 file changed, 29 insertions(+), 37 deletions(-) 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 9154c32701..63eaab23f2 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 @@ -32,10 +32,10 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasRuleEngineProfile; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.AssetId; @@ -178,15 +178,8 @@ public class DefaultTbClusterService implements TbClusterService { return; } } else { - if (entityId.getEntityType().equals(EntityType.DEVICE)) { - tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceId(entityId.getId()))); - } else if (entityId.getEntityType().equals(EntityType.DEVICE_PROFILE)) { - tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId()))); - } else if (entityId.getEntityType().equals(EntityType.ASSET)) { - tbMsg = transformMsg(tbMsg, assetProfileCache.get(tenantId, new AssetId(entityId.getId()))); - } else if (entityId.getEntityType().equals(EntityType.ASSET_PROFILE)) { - tbMsg = transformMsg(tbMsg, assetProfileCache.get(tenantId, new AssetProfileId(entityId.getId()))); - } + HasRuleEngineProfile ruleEngineProfile = getRuleEngineProfileForEntityOrElseNull(tenantId, entityId); + tbMsg = transformMsg(tbMsg, ruleEngineProfile); } TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, entityId); log.trace("PUSHING msg: {} to:{}", tbMsg, tpi); @@ -198,34 +191,33 @@ public class DefaultTbClusterService implements TbClusterService { toRuleEngineMsgs.incrementAndGet(); } - private TbMsg transformMsg(TbMsg tbMsg, DeviceProfile deviceProfile) { - if (deviceProfile != null) { - RuleChainId targetRuleChainId = deviceProfile.getDefaultRuleChainId(); - String targetQueueName = deviceProfile.getDefaultQueueName(); - tbMsg = transformMsg(tbMsg, targetRuleChainId, targetQueueName); - } - return tbMsg; - } - - private TbMsg transformMsg(TbMsg tbMsg, AssetProfile assetProfile) { - if (assetProfile != null) { - RuleChainId targetRuleChainId = assetProfile.getDefaultRuleChainId(); - String targetQueueName = assetProfile.getDefaultQueueName(); - tbMsg = transformMsg(tbMsg, targetRuleChainId, targetQueueName); + private HasRuleEngineProfile getRuleEngineProfileForEntityOrElseNull(TenantId tenantId, EntityId entityId) { + if (entityId.getEntityType().equals(EntityType.DEVICE)) { + return deviceProfileCache.get(tenantId, new DeviceId(entityId.getId())); + } else if (entityId.getEntityType().equals(EntityType.DEVICE_PROFILE)) { + return deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId())); + } else if (entityId.getEntityType().equals(EntityType.ASSET)) { + return assetProfileCache.get(tenantId, new AssetId(entityId.getId())); + } else if (entityId.getEntityType().equals(EntityType.ASSET_PROFILE)) { + return assetProfileCache.get(tenantId, new AssetProfileId(entityId.getId())); } - return tbMsg; - } - - private TbMsg transformMsg(TbMsg tbMsg, RuleChainId targetRuleChainId, String targetQueueName) { - boolean isRuleChainTransform = targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId()); - boolean isQueueTransform = targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName()); - - if (isRuleChainTransform && isQueueTransform) { - tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName); - } else if (isRuleChainTransform) { - tbMsg = TbMsg.transformMsgRuleChainId(tbMsg, targetRuleChainId); - } else if (isQueueTransform) { - tbMsg = TbMsg.transformMsgQueueName(tbMsg, targetQueueName); + return null; + } + private TbMsg transformMsg(TbMsg tbMsg, HasRuleEngineProfile ruleEngineProfile) { + if (ruleEngineProfile != null) { + RuleChainId targetRuleChainId = ruleEngineProfile.getDefaultRuleChainId(); + String targetQueueName = ruleEngineProfile.getDefaultQueueName(); + + boolean isRuleChainTransform = targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId()); + boolean isQueueTransform = targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName()); + + if (isRuleChainTransform && isQueueTransform) { + tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName); + } else if (isRuleChainTransform) { + tbMsg = TbMsg.transformMsgRuleChainId(tbMsg, targetRuleChainId); + } else if (isQueueTransform) { + tbMsg = TbMsg.transformMsgQueueName(tbMsg, targetQueueName); + } } return tbMsg; } From b69f63660b5e8a9e1b8c1a0e90d35d9a9253fe41 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 27 Jul 2023 16:59:26 +0300 Subject: [PATCH 129/168] UI: Device connectivity change coap install instruction and added support spartplug --- ...e-check-connectivity-dialog.component.html | 168 ++++++++++-------- ...e-check-connectivity-dialog.component.scss | 5 +- ...ice-check-connectivity-dialog.component.ts | 42 +---- ui-ngx/src/app/shared/models/device.models.ts | 1 + .../assets/locale/locale.constant-en_US.json | 2 + 5 files changed, 100 insertions(+), 118 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html index 0f9c6dc055..71a8364134 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html @@ -73,7 +73,7 @@
device.connectivity.install-necessary-client-tools
-
device.connectivity.install-curl-windows
+
device.connectivity.install-curl-windows
-
device.connectivity.use-following-instructions
- - - - - Windows - - -
-
-
device.connectivity.install-necessary-client-tools
-
- + + +
+ +
device.connectivity.use-following-instructions
+ + + + + Windows + + +
+
+
device.connectivity.install-necessary-client-tools
+
+ - + +
-
- - -
- - - - - - MacOS - - -
-
-
device.connectivity.install-necessary-client-tools
- +
- + MacOS + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ - -
-
- - - - - Linux - - -
-
-
device.connectivity.install-necessary-client-tools
- +
- + Linux + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ - -
-
- - - - - Docker - - -
- + + + Docker + + +
+ - -
-
- - +
+
+
+
+ +
device.connectivity.use-following-instructions
@@ -226,10 +234,14 @@
-
+
device.connectivity.install-necessary-client-tools
- +
+ + +
-
+
device.connectivity.install-necessary-client-tools
- +
+ + +
Date: Thu, 27 Jul 2023 17:18:39 +0300 Subject: [PATCH 130/168] UI: Implement Value card widget settings. Improve widget container layout. --- .../json/system/widget_bundles/cards.json | 16 +- .../core/services/dashboard-utils.service.ts | 6 +- .../add-widget-dialog.component.scss | 4 +- .../dashboard-page.component.ts | 1 + .../dashboard-widget-select.component.scss | 2 + .../value-card-basic-config.component.html | 46 ++-- .../value-card-basic-config.component.ts | 10 + .../basic/common/data-key-row.component.html | 6 +- .../basic/common/data-key-row.component.scss | 13 +- .../common/data-keys-panel.component.html | 4 +- .../common/data-keys-panel.component.scss | 12 +- .../widget/config/data-keys.component.html | 2 +- .../widget/config/data-keys.component.scss | 8 +- .../config/widget-settings.component.ts | 10 + .../widget/config/widget-settings.models.ts | 20 +- .../value-card-widget-settings.component.html | 89 ++++++++ .../value-card-widget-settings.component.ts | 200 ++++++++++++++++++ .../background-settings-panel.component.html | 87 ++++++++ .../background-settings-panel.component.scss | 73 +++++++ .../background-settings-panel.component.ts | 120 +++++++++++ .../common/background-settings.component.html | 30 +++ .../common/background-settings.component.scss | 41 ++++ .../common/background-settings.component.ts | 120 +++++++++++ .../common/image-cards-select.component.ts | 27 ++- .../lib/settings/widget-settings.module.ts | 20 +- .../widget/widget-component.service.ts | 3 + .../widget/widget-config.component.html | 1 + .../widget/widget-container.component.html | 17 +- .../widget/widget-container.component.scss | 47 ++-- .../components/widget/widget.component.ts | 1 + .../home/models/widget-component.models.ts | 2 + .../components/unit-input.component.html | 2 +- .../shared/components/unit-input.component.ts | 19 +- ui-ngx/src/app/shared/models/unit.models.ts | 6 + ui-ngx/src/app/shared/models/widget.models.ts | 18 +- .../assets/locale/locale.constant-en_US.json | 16 +- .../src/assets/{model => metadata}/units.json | 0 ui-ngx/src/styles.scss | 25 +-- 38 files changed, 1020 insertions(+), 104 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts rename ui-ngx/src/assets/{model => metadata}/units.json (100%) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index cc2c74c359..1289923667 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -229,19 +229,19 @@ { "alias": "value_card", "name": "Value card", - "image": null, + "image": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyNyIgZmlsbD0ibm9uZSIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTI4IDEyNyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KIDxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfMTE0Ml8yMDM5NTQpIj4KICA8cmVjdCB4PSI1LjUiIHk9IjIuNSIgd2lkdGg9IjExNyIgaGVpZ2h0PSIxMTciIHJ4PSIyLjI5NDEiIGZpbGw9IiNmZmYiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPgogIDxwYXRoIGQ9Im0zMy42MDMgMjkuMjIxdi03LjY0NzFjMC0xLjU4NjgtMS4yODA4LTIuODY3Ni0yLjg2NzYtMi44Njc2cy0yLjg2NzcgMS4yODA4LTIuODY3NyAyLjg2NzZ2Ny42NDcxYy0xLjE1NjYgMC44Njk4LTEuOTExNyAyLjI2NTQtMS45MTE3IDMuODIzNSAwIDIuNjM4MiAyLjE0MTIgNC43Nzk0IDQuNzc5NCA0Ljc3OTRzNC43Nzk0LTIuMTQxMiA0Ljc3OTQtNC43Nzk0YzAtMS41NTgxLTAuNzU1MS0yLjk1MzctMS45MTE4LTMuODIzNXptLTMuODIzNS03LjY0NzFjMC0wLjUyNTcgMC40MzAyLTAuOTU1OSAwLjk1NTktMC45NTU5czAuOTU1OSAwLjQzMDIgMC45NTU5IDAuOTU1OWgtMC45NTU5djAuOTU1OWgwLjk1NTl2MS45MTE3aC0wLjk1NTl2MC45NTU5aDAuOTU1OXYxLjkxMThoLTEuOTExOHYtNS43MzUzeiIgZmlsbD0iIzU0NjlGRiIvPgogIDxnIGZpbGw9IiMwMDAiPgogICA8cGF0aCBkPSJtNTAuMTQxIDE5Ljc0MXY2LjUyMzhoLTEuMTE1N3YtNi41MjM4aDEuMTE1N3ptMi4wNDc3IDB2MC44OTYxaC01LjE5MzJ2LTAuODk2MWg1LjE5MzJ6bTIuNjAzMyA2LjYxMzVjLTAuMzU4NSAwLTAuNjgyNi0wLjA1ODMtMC45NzIzLTAuMTc0OC0wLjI4NjgtMC4xMTk1LTAuNTMxOC0wLjI4NTMtMC43MzQ5LTAuNDk3My0wLjIwMDEtMC4yMTIxLTAuMzU0LTAuNDYxNi0wLjQ2MTUtMC43NDgzLTAuMTA3NS0wLjI4NjgtMC4xNjEzLTAuNTk2LTAuMTYxMy0wLjkyNzV2LTAuMTc5M2MwLTAuMzc5MyAwLjA1NTMtMC43MjI4IDAuMTY1OC0xLjAzMDVzMC4yNjQzLTAuNTcwNiAwLjQ2MTUtMC43ODg2YzAuMTk3MS0wLjIyMTEgMC40MzAxLTAuMzg5OCAwLjY5OS0wLjUwNjMgMC4yNjg4LTAuMTE2NSAwLjU2MDEtMC4xNzQ4IDAuODczNy0wLjE3NDggMC4zNDY1IDAgMC42NDk3IDAuMDU4MyAwLjkwOTYgMC4xNzQ4czAuNDc1IDAuMjgwOCAwLjY0NTIgMC40OTI4YzAuMTczMyAwLjIwOTEgMC4zMDE3IDAuNDU4NiAwLjM4NTQgMC43NDgzIDAuMDg2NiAwLjI4OTggMC4xMjk5IDAuNjA5NCAwLjEyOTkgMC45NTg5djAuNDYxNWgtMy43NDU5di0wLjc3NTJoMi42Nzk1di0wLjA4NTFjLTZlLTMgLTAuMTk0Mi0wLjA0NDgtMC4zNzY0LTAuMTE2NS0wLjU0NjYtMC4wNjg3LTAuMTcwMy0wLjE3NDctMC4zMDc3LTAuMzE4MS0wLjQxMjMtMC4xNDM0LTAuMTA0NS0wLjMzNDYtMC4xNTY4LTAuNTczNi0wLjE1NjgtMC4xNzkyIDAtMC4zMzkgMC4wMzg4LTAuNDc5NCAwLjExNjUtMC4xMzc0IDAuMDc0Ny0wLjI1MjQgMC4xODM3LTAuMzQ1IDAuMzI3MXMtMC4xNjQzIDAuMzE2Ni0wLjIxNTEgMC41MTk4Yy0wLjA0NzggMC4yMDAxLTAuMDcxNyAwLjQyNTYtMC4wNzE3IDAuNjc2NXYwLjE3OTNjMCAwLjIxMjEgMC4wMjg0IDAuNDA5MiAwLjA4NTIgMC41OTE0IDAuMDU5NyAwLjE3OTMgMC4xNDYzIDAuMzM2MSAwLjI1OTggMC40NzA1IDAuMTEzNiAwLjEzNDQgMC4yNTEgMC4yNDA1IDAuNDEyMyAwLjMxODEgMC4xNjEzIDAuMDc0NyAwLjM0NSAwLjExMiAwLjU1MTEgMC4xMTIgMC4yNTk5IDAgMC40OTE0LTAuMDUyMiAwLjY5NDUtMC4xNTY4IDAuMjAzMS0wLjEwNDUgMC4zNzk0LTAuMjUyNCAwLjUyODctMC40NDM2bDAuNTY5MSAwLjU1MTJjLTAuMTA0NiAwLjE1MjMtMC4yNDA1IDAuMjk4Ny0wLjQwNzggMC40MzkxLTAuMTY3MyAwLjEzNzQtMC4zNzE5IDAuMjQ5NC0wLjYxMzggMC4zMzYtMC4yMzkgMC4wODY2LTAuNTE2OCAwLjEzLTAuODMzNCAwLjEzem00LjAwNTctMy45NTJ2My44NjIzaC0xLjA3OTh2LTQuODQ4MWgxLjAxNzFsMC4wNjI3IDAuOTg1OHptLTAuMTc0NyAxLjI1OTEtMC4zNjc1LTAuMDA0NWMwLTAuMzM0NiAwLjA0MTktMC42NDM3IDAuMTI1NS0wLjkyNzVzMC4yMDYxLTAuNTMwMiAwLjM2NzQtMC43MzkzYzAuMTYxMy0wLjIxMjEgMC4zNjE1LTAuMzc0OSAwLjYwMDQtMC40ODg0IDAuMjQyLTAuMTE2NSAwLjUyMTMtMC4xNzQ4IDAuODM3OS0wLjE3NDggMC4yMjExIDAgMC40MjI3IDAuMDMyOSAwLjYwNDkgMC4wOTg2IDAuMTg1MiAwLjA2MjcgMC4zNDUgMC4xNjI4IDAuNDc5NSAwLjMwMDIgMC4xMzc0IDAuMTM3NCAwLjI0MTkgMC4zMTM2IDAuMzEzNiAwLjUyODcgMC4wNzQ3IDAuMjE1MSAwLjExMiAwLjQ3NSAwLjExMiAwLjc3OTd2My4yMzA1aC0xLjA3OTh2LTMuMTM2NGMwLTAuMjM2LTAuMDM1OS0wLjQyMTItMC4xMDc2LTAuNTU1Ni0wLjA2ODctMC4xMzQ1LTAuMTY4Ny0wLjIzMDEtMC4zMDAyLTAuMjg2OC0wLjEyODQtMC4wNTk4LTAuMjgyMy0wLjA4OTYtMC40NjE1LTAuMDg5Ni0wLjIwMzEgMC0wLjM3NjQgMC4wMzg4LTAuNTE5NyAwLjExNjUtMC4xNDA0IDAuMDc3Ni0wLjI1NTQgMC4xODM3LTAuMzQ1MSAwLjMxODEtMC4wODk2IDAuMTM0NC0wLjE1NTMgMC4yODk4LTAuMTk3MSAwLjQ2NnMtMC4wNjI3IDAuMzY0NC0wLjA2MjcgMC41NjQ2em0zLjAwNjUtMC4yODY4LTAuNTA2MyAwLjExMmMwLTAuMjkyNyAwLjA0MDMtMC41NjkgMC4xMjEtMC44Mjg5IDAuMDgzNi0wLjI2MjkgMC4yMDQ2LTAuNDkyOSAwLjM2MjktMC42OSAwLjE2MTMtMC4yMDAyIDAuMzYtMC4zNTcgMC41OTU5LTAuNDcwNSAwLjIzNi0wLjExMzUgMC41MDY0LTAuMTcwMyAwLjgxMS0wLjE3MDMgMC4yNDggMCAwLjQ2OSAwLjAzNDQgMC42NjMyIDAuMTAzMSAwLjE5NzEgMC4wNjU3IDAuMzY0NCAwLjE3MDIgMC41MDE4IDAuMzEzNnMwLjI0MiAwLjMzMDEgMC4zMTM3IDAuNTYwMWMwLjA3MTcgMC4yMjcgMC4xMDc1IDAuNTAxOCAwLjEwNzUgMC44MjQ1djMuMTM2NGgtMS4wODQzdi0zLjE0MDljMC0wLjI0NS0wLjAzNTktMC40MzQ2LTAuMTA3Ni0wLjU2OTEtMC4wNjg3LTAuMTM0NC0wLjE2NzItMC4yMjctMC4yOTU3LTAuMjc3OC0wLjEyODQtMC4wNTM3LTAuMjgyMy0wLjA4MDYtMC40NjE1LTAuMDgwNi0wLjE2NzMgMC0wLjMxNTEgMC4wMzEzLTAuNDQzNiAwLjA5NDEtMC4xMjU0IDAuMDU5Ny0wLjIzMTUgMC4xNDQ4LTAuMzE4MSAwLjI1NTQtMC4wODY2IDAuMTA3NS0wLjE1MjQgMC4yMzE1LTAuMTk3MiAwLjM3MTktMC4wNDE4IDAuMTQwNC0wLjA2MjcgMC4yOTI3LTAuMDYyNyAwLjQ1N3ptNS4zMDk2LTEuMDI2MXY1Ljc4MDFoLTEuMDc5OHYtNi43MTIxaDAuOTk0N2wwLjA4NTEgMC45MzJ6bTMuMTU4OSAxLjQ0NzN2MC4wOTQxYzAgMC4zNTI1LTAuMDQxOCAwLjY3OTYtMC4xMjU0IDAuOTgxMy0wLjA4MDcgMC4yOTg3LTAuMjAxNyAwLjU2LTAuMzYzIDAuNzg0MS0wLjE1ODMgMC4yMjEtMC4zNTM5IDAuMzkyOC0wLjU4NjkgMC41MTUzLTAuMjMzIDAuMTIyNC0wLjUwMTkgMC4xODM3LTAuODA2NiAwLjE4MzctMC4zMDE3IDAtMC41NjYtMC4wNTUzLTAuNzkzLTAuMTY1OC0wLjIyNDEtMC4xMTM1LTAuNDEzOC0wLjI3MzMtMC41NjkxLTAuNDc5NS0wLjE1NTMtMC4yMDYxLTAuMjgwOC0wLjQ0OC0wLjM3NjQtMC43MjU4LTAuMDkyNi0wLjI4MDgtMC4xNTgzLTAuNTg4NS0wLjE5NzEtMC45MjMxdi0wLjM2MjljMC4wMzg4LTAuMzU1NSAwLjEwNDUtMC42NzgxIDAuMTk3MS0wLjk2NzggMC4wOTU2LTAuMjg5OCAwLjIyMTEtMC41MzkyIDAuMzc2NC0wLjc0ODNzMC4zNDUtMC4zNzA0IDAuNTY5MS0wLjQ4MzljMC4yMjQtMC4xMTM1IDAuNDg1NC0wLjE3MDMgMC43ODQxLTAuMTcwMyAwLjMwNDcgMCAwLjU3NSAwLjA1OTggMC44MTEgMC4xNzkyIDAuMjM2IDAuMTE2NSAwLjQzNDYgMC4yODM4IDAuNTk1OSAwLjUwMTkgMC4xNjEzIDAuMjE1MSAwLjI4MjMgMC40NzQ5IDAuMzYyOSAwLjc3OTYgMC4wODA3IDAuMzAxNyAwLjEyMSAwLjYzNzggMC4xMjEgMS4wMDgyem0tMS4wNzk4IDAuMDk0MXYtMC4wOTQxYzAtMC4yMjQxLTAuMDIwOS0wLjQzMTctMC4wNjI3LTAuNjIyOC0wLjA0MTktMC4xOTQyLTAuMTA3Ni0wLjM2NDUtMC4xOTcyLTAuNTEwOC0wLjA4OTYtMC4xNDY0LTAuMjA0Ni0wLjI1OTktMC4zNDUtMC4zNDA2LTAuMTM3NC0wLjA4MzYtMC4zMDMyLTAuMTI1NC0wLjQ5NzQtMC4xMjU0LTAuMTkxMSAwLTAuMzU1NCAwLjAzMjgtMC40OTI4IDAuMDk4NS0wLjEzNzUgMC4wNjI4LTAuMjUyNSAwLjE1MDktMC4zNDUxIDAuMjY0NHMtMC4xNjQzIDAuMjQ2NC0wLjIxNSAwLjM5ODhjLTAuMDUwOCAwLjE0OTMtMC4wODY3IDAuMzEyMS0wLjEwNzYgMC40ODg0djAuODY5MmMwLjAzNTkgMC4yMTUxIDAuMDk3MSAwLjQxMjMgMC4xODM3IDAuNTkxNSAwLjA4NjcgMC4xNzkyIDAuMjA5MSAwLjMyMjYgMC4zNjc1IDAuNDMwMSAwLjE2MTMgMC4xMDQ2IDAuMzY3NCAwLjE1NjkgMC42MTgzIDAuMTU2OSAwLjE5NDIgMCAwLjM1OTktMC4wNDE5IDAuNDk3My0wLjEyNTUgMC4xMzc1LTAuMDgzNiAwLjI0OTUtMC4xOTg2IDAuMzM2MS0wLjM0NSAwLjA4OTYtMC4xNDk0IDAuMTU1My0wLjMyMTEgMC4xOTcyLTAuNTE1MyAwLjA0MTgtMC4xOTQxIDAuMDYyNy0wLjQwMDMgMC4wNjI3LTAuNjE4M3ptNC4yNzkgMi40NjQ0Yy0wLjM1ODQgMC0wLjY4MjUtMC4wNTgzLTAuOTcyMy0wLjE3NDgtMC4yODY3LTAuMTE5NS0wLjUzMTctMC4yODUzLTAuNzM0OC0wLjQ5NzMtMC4yMDAxLTAuMjEyMS0wLjM1NC0wLjQ2MTYtMC40NjE1LTAuNzQ4My0wLjEwNzUtMC4yODY4LTAuMTYxMy0wLjU5Ni0wLjE2MTMtMC45Mjc1di0wLjE3OTNjMC0wLjM3OTMgMC4wNTUyLTAuNzIyOCAwLjE2NTgtMS4wMzA1IDAuMTEwNS0wLjMwNzcgMC4yNjQzLTAuNTcwNiAwLjQ2MTUtMC43ODg2IDAuMTk3MS0wLjIyMTEgMC40MzAxLTAuMzg5OCAwLjY5OS0wLjUwNjMgMC4yNjg4LTAuMTE2NSAwLjU2MDEtMC4xNzQ4IDAuODczNy0wLjE3NDggMC4zNDY1IDAgMC42NDk3IDAuMDU4MyAwLjkwOTYgMC4xNzQ4czAuNDc0OSAwLjI4MDggMC42NDUyIDAuNDkyOGMwLjE3MzMgMC4yMDkxIDAuMzAxNyAwLjQ1ODYgMC4zODUzIDAuNzQ4MyAwLjA4NjcgMC4yODk4IDAuMTMgMC42MDk0IDAuMTMgMC45NTg5djAuNDYxNWgtMy43NDU5di0wLjc3NTJoMi42Nzk1di0wLjA4NTFjLTZlLTMgLTAuMTk0Mi0wLjA0NDgtMC4zNzY0LTAuMTE2NS0wLjU0NjYtMC4wNjg3LTAuMTcwMy0wLjE3NDgtMC4zMDc3LTAuMzE4MS0wLjQxMjMtMC4xNDM0LTAuMTA0NS0wLjMzNDYtMC4xNTY4LTAuNTczNi0wLjE1NjgtMC4xNzkyIDAtMC4zMzkgMC4wMzg4LTAuNDc5NCAwLjExNjUtMC4xMzc0IDAuMDc0Ny0wLjI1MjQgMC4xODM3LTAuMzQ1IDAuMzI3MXMtMC4xNjQzIDAuMzE2Ni0wLjIxNTEgMC41MTk4Yy0wLjA0NzggMC4yMDAxLTAuMDcxNyAwLjQyNTYtMC4wNzE3IDAuNjc2NXYwLjE3OTNjMCAwLjIxMjEgMC4wMjg0IDAuNDA5MiAwLjA4NTEgMC41OTE0IDAuMDU5OCAwLjE3OTMgMC4xNDY0IDAuMzM2MSAwLjI1OTkgMC40NzA1czAuMjUwOSAwLjI0MDUgMC40MTIzIDAuMzE4MWMwLjE2MTMgMC4wNzQ3IDAuMzQ1IDAuMTEyIDAuNTUxMSAwLjExMiAwLjI1OTkgMCAwLjQ5MTQtMC4wNTIyIDAuNjk0NS0wLjE1NjggMC4yMDMxLTAuMTA0NSAwLjM3OTQtMC4yNTI0IDAuNTI4Ny0wLjQ0MzZsMC41NjkxIDAuNTUxMmMtMC4xMDQ2IDAuMTUyMy0wLjI0MDUgMC4yOTg3LTAuNDA3OCAwLjQzOTEtMC4xNjczIDAuMTM3NC0wLjM3MTkgMC4yNDk0LTAuNjEzOCAwLjMzNi0wLjIzOSAwLjA4NjYtMC41MTY4IDAuMTMtMC44MzM1IDAuMTN6bTQuMDEwMy00LjAxNDd2My45MjVoLTEuMDc5OXYtNC44NDgxaDEuMDMwNmwwLjA0OTMgMC45MjMxem0xLjQ4MzEtMC45NTQ0LTllLTMgMS4wMDM2Yy0wLjA2NTctMC4wMTE5LTAuMTM3NC0wLjAyMDktMC4yMTUxLTAuMDI2OC0wLjA3NDYtNmUtMyAtMC4xNDkzLTllLTMgLTAuMjI0LTllLTMgLTAuMTg1MiAwLTAuMzQ4IDAuMDI2OS0wLjQ4ODQgMC4wODA3LTAuMTQwNCAwLjA1MDctMC4yNTg0IDAuMTI1NC0wLjM1NCAwLjIyNC0wLjA5MjYgMC4wOTU2LTAuMTY0MyAwLjIxMjEtMC4yMTUgMC4zNDk1LTAuMDUwOCAwLjEzNzQtMC4wODA3IDAuMjkxMi0wLjA4OTYgMC40NjE1bC0wLjI0NjUgMC4wMTc5YzAtMC4zMDQ3IDAuMDI5OS0wLjU4NyAwLjA4OTYtMC44NDY4IDAuMDU5OC0wLjI1OTkgMC4xNDk0LTAuNDg4NCAwLjI2ODktMC42ODU2IDAuMTIyNC0wLjE5NzEgMC4yNzQ4LTAuMzUxIDAuNDU3LTAuNDYxNSAwLjE4NTItMC4xMTA1IDAuMzk4OC0wLjE2NTggMC42NDA3LTAuMTY1OCAwLjA2NTggMCAwLjEzNiA2ZS0zIDAuMjEwNiAwLjAxNzkgMC4wNzc3IDAuMDEyIDAuMTM2IDAuMDI1NCAwLjE3NDggMC4wNDA0em0zLjM5MTkgMy45MDcxdi0yLjMxMmMwLTAuMTczMy0wLjAzMTQtMC4zMjI2LTAuMDk0MS0wLjQ0ODEtMC4wNjI4LTAuMTI1NC0wLjE1ODMtMC4yMjI1LTAuMjg2OC0wLjI5MTItMC4xMjU0LTAuMDY4Ny0wLjI4MzgtMC4xMDMxLTAuNDc0OS0wLjEwMzEtMC4xNzYzIDAtMC4zMjg2IDAuMDI5OS0wLjQ1NzEgMC4wODk2LTAuMTI4NCAwLjA1OTgtMC4yMjg1IDAuMTQwNC0wLjMwMDIgMC4yNDJzLTAuMTA3NSAwLjIxNjYtMC4xMDc1IDAuMzQ1aC0xLjA3NTRjMC0wLjE5MTIgMC4wNDYzLTAuMzc2NCAwLjEzODktMC41NTU2czAuMjI3LTAuMzM5IDAuNDAzMy0wLjQ3OTRjMC4xNzYyLTAuMTQwNCAwLjM4NjgtMC4yNTA5IDAuNjMxOC0wLjMzMTYgMC4yNDQ5LTAuMDgwNyAwLjUxOTctMC4xMjEgMC44MjQ0LTAuMTIxIDAuMzY0NCAwIDAuNjg3IDAuMDYxMyAwLjk2NzggMC4xODM3IDAuMjgzOCAwLjEyMjUgMC41MDY0IDAuMzA3NyAwLjY2NzcgMC41NTU2IDAuMTY0MyAwLjI0NSAwLjI0NjQgMC41NTI3IDAuMjQ2NCAwLjkyMzF2Mi4xNTUyYzAgMC4yMjEgMC4wMTQ5IDAuNDE5NyAwLjA0NDggMC41OTU5IDAuMDMyOSAwLjE3MzMgMC4wNzkyIDAuMzI0MSAwLjEzODkgMC40NTI2djAuMDcxNmgtMS4xMDY3Yy0wLjA1MDgtMC4xMTY0LTAuMDkxMS0wLjI2NDMtMC4xMjEtMC40NDM1LTAuMDI2OS0wLjE4MjMtMC4wNDAzLTAuMzU4NS0wLjA0MDMtMC41Mjg4em0wLjE1NjgtMS45NzYgOWUtMyAwLjY2NzdoLTAuNzc1MmMtMC4yMDAxIDAtMC4zNzY0IDAuMDE5NC0wLjUyODcgMC4wNTgyLTAuMTUyNCAwLjAzNTktMC4yNzkzIDAuMDg5Ni0wLjM4MDkgMC4xNjEzLTAuMTAxNSAwLjA3MTctMC4xNzc3IDAuMTU4My0wLjIyODUgMC4yNTk5cy0wLjA3NjIgMC4yMTY2LTAuMDc2MiAwLjM0NWMwIDAuMTI4NSAwLjAyOTkgMC4yNDY1IDAuMDg5NiAwLjM1NCAwLjA1OTggMC4xMDQ1IDAuMTQ2NCAwLjE4NjcgMC4yNTk5IDAuMjQ2NCAwLjExNjUgMC4wNTk4IDAuMjU2OSAwLjA4OTYgMC40MjEyIDAuMDg5NiAwLjIyMTEgMCAwLjQxMzctMC4wNDQ4IDAuNTc4LTAuMTM0NCAwLjE2NzMtMC4wOTI2IDAuMjk4Ny0wLjIwNDYgMC4zOTQzLTAuMzM2IDAuMDk1Ni0wLjEzNDQgMC4xNDY0LTAuMjYxNCAwLjE1MjQtMC4zODA5bDAuMzQ5NSAwLjQ3OTVjLTAuMDM1OSAwLjEyMjQtMC4wOTcxIDAuMjUzOS0wLjE4MzggMC4zOTQzLTAuMDg2NiAwLjE0MDMtMC4yMDAxIDAuMjc0OC0wLjM0MDUgMC40MDMyLTAuMTM3NCAwLjEyNTUtMC4zMDMyIDAuMjI4NS0wLjQ5NzMgMC4zMDkyLTAuMTkxMiAwLjA4MDYtMC40MTIzIDAuMTIxLTAuNjYzMiAwLjEyMS0wLjMxNjYgMC0wLjU5ODktMC4wNjI4LTAuODQ2OC0wLjE4ODItMC4yNDgtMC4xMjg1LTAuNDQyMS0wLjMwMDItMC41ODI1LTAuNTE1My0wLjE0MDQtMC4yMTgxLTAuMjEwNi0wLjQ2NDUtMC4yMTA2LTAuNzM5MyAwLTAuMjU2OSAwLjA0NzgtMC40ODM5IDAuMTQzNC0wLjY4MTEgMC4wOTg1LTAuMjAwMSAwLjI0MTktMC4zNjc0IDAuNDMwMS0wLjUwMTggMC4xOTEyLTAuMTM0NCAwLjQyNDItMC4yMzYgMC42OTktMC4zMDQ3IDAuMjc0OC0wLjA3MTcgMC41ODg1LTAuMTA3NiAwLjk0MDktMC4xMDc2aDAuODQ2OXptNC40MjI0LTEuODk5OHYwLjc4ODZoLTIuNzMzMnYtMC43ODg2aDIuNzMzMnptLTEuOTQ0Ni0xLjE4NzRoMS4wNzk5djQuNjk1OGMwIDAuMTQ5NCAwLjAyMDkgMC4yNjQ0IDAuMDYyNyAwLjM0NSAwLjA0NDggMC4wNzc3IDAuMTA2IDAuMTMgMC4xODM3IDAuMTU2OSAwLjA3NzcgMC4wMjY4IDAuMTY4OCAwLjA0MDMgMC4yNzMzIDAuMDQwMyAwLjA3NDcgMCAwLjE0NjQtMC4wMDQ1IDAuMjE1MS0wLjAxMzUgMC4wNjg3LTAuMDA4OSAwLjEyNC0wLjAxNzkgMC4xNjU4LTAuMDI2OGwwLjAwNDUgMC44MjQ0Yy0wLjA4OTYgMC4wMjY5LTAuMTk0MiAwLjA1MDgtMC4zMTM3IDAuMDcxNy0wLjExNjUgMC4wMjA5LTAuMjUwOSAwLjAzMTQtMC40MDMyIDAuMDMxNC0wLjI0OCAwLTAuNDY3NS0wLjA0MzQtMC42NTg3LTAuMTMtMC4xOTEyLTAuMDg5Ni0wLjM0MDUtMC4yMzQ1LTAuNDQ4MS0wLjQzNDYtMC4xMDc1LTAuMjAwMS0wLjE2MTMtMC40NjYtMC4xNjEzLTAuNzk3NnYtNC43NjN6bTUuODM4NCA0Ljg5M3YtMy43MDU2aDEuMDg0M3Y0Ljg0ODFoLTEuMDIxNmwtMC4wNjI3LTEuMTQyNXptMC4xNTIzLTEuMDA4MiAwLjM2My0wLjAwODljMCAwLjMyNTUtMC4wMzU5IDAuNjI1OC0wLjEwNzYgMC45MDA2LTAuMDcxNyAwLjI3MTgtMC4xODIyIDAuNTA5My0wLjMzMTYgMC43MTI0LTAuMTQ5MyAwLjIwMDEtMC4zNDA1IDAuMzU3LTAuNTczNSAwLjQ3MDUtMC4yMzMgMC4xMTA1LTAuNTEyMyAwLjE2NTgtMC44Mzc5IDAuMTY1OC0wLjIzNiAwLTAuNDUyNS0wLjAzNDQtMC42NDk3LTAuMTAzMS0wLjE5NzEtMC4wNjg3LTAuMzY3NC0wLjE3NDctMC41MTA4LTAuMzE4MS0wLjE0MDQtMC4xNDM0LTAuMjQ5NC0wLjMzMDEtMC4zMjcxLTAuNTYwMS0wLjA3NzYtMC4yMy0wLjExNjUtMC41MDQ4LTAuMTE2NS0wLjgyNDV2LTMuMTMyaDEuMDc5OXYzLjE0MWMwIDAuMTc2MiAwLjAyMDkgMC4zMjQxIDAuMDYyNyAwLjQ0MzYgMC4wNDE4IDAuMTE2NSAwLjA5ODYgMC4yMTA2IDAuMTcwMyAwLjI4MjNzMC4xNTUzIDAuMTIyNCAwLjI1MDkgMC4xNTIzIDAuMTk3MSAwLjA0NDggMC4zMDQ3IDAuMDQ0OGMwLjMwNzcgMCAwLjU0OTYtMC4wNTk3IDAuNzI1OS0wLjE3OTIgMC4xNzkyLTAuMTIyNSAwLjMwNjEtMC4yODY4IDAuMzgwOC0wLjQ5MjkgMC4wNzc3LTAuMjA2MSAwLjExNjUtMC40Mzc2IDAuMTE2NS0wLjY5NDV6bTMuMjY2NC0xLjc3NDN2My45MjVoLTEuMDc5OHYtNC44NDgxaDEuMDMwNmwwLjA0OTIgMC45MjMxem0xLjQ4MzItMC45NTQ0LTllLTMgMS4wMDM2Yy0wLjA2NTctMC4wMTE5LTAuMTM3NC0wLjAyMDktMC4yMTUxLTAuMDI2OC0wLjA3NDctNmUtMyAtMC4xNDkzLTllLTMgLTAuMjI0LTllLTMgLTAuMTg1MiAwLTAuMzQ4IDAuMDI2OS0wLjQ4ODQgMC4wODA3LTAuMTQwNCAwLjA1MDctMC4yNTg0IDAuMTI1NC0wLjM1NCAwLjIyNC0wLjA5MjYgMC4wOTU2LTAuMTY0MyAwLjIxMjEtMC4yMTUxIDAuMzQ5NS0wLjA1MDcgMC4xMzc0LTAuMDgwNiAwLjI5MTItMC4wODk2IDAuNDYxNWwtMC4yNDY0IDAuMDE3OWMwLTAuMzA0NyAwLjAyOTktMC41ODcgMC4wODk2LTAuODQ2OCAwLjA1OTctMC4yNTk5IDAuMTQ5NC0wLjQ4ODQgMC4yNjg4LTAuNjg1NiAwLjEyMjUtMC4xOTcxIDAuMjc0OS0wLjM1MSAwLjQ1NzEtMC40NjE1IDAuMTg1Mi0wLjExMDUgMC4zOTg4LTAuMTY1OCAwLjY0MDctMC4xNjU4IDAuMDY1NyAwIDAuMTM1OSA2ZS0zIDAuMjEwNiAwLjAxNzkgMC4wNzc3IDAuMDEyIDAuMTM1OSAwLjAyNTQgMC4xNzQ4IDAuMDQwNHptMi44Njc2IDQuOTY5MWMtMC4zNTg1IDAtMC42ODI2LTAuMDU4My0wLjk3MjMtMC4xNzQ4LTAuMjg2OC0wLjExOTUtMC41MzE3LTAuMjg1My0wLjczNDgtMC40OTczLTAuMjAwMi0wLjIxMjEtMC4zNTQtMC40NjE2LTAuNDYxNi0wLjc0ODMtMC4xMDc1LTAuMjg2OC0wLjE2MTMtMC41OTYtMC4xNjEzLTAuOTI3NXYtMC4xNzkzYzAtMC4zNzkzIDAuMDU1My0wLjcyMjggMC4xNjU4LTEuMDMwNSAwLjExMDYtMC4zMDc3IDAuMjY0NC0wLjU3MDYgMC40NjE1LTAuNzg4NiAwLjE5NzItMC4yMjExIDAuNDMwMi0wLjM4OTggMC42OTktMC41MDYzIDAuMjY4OS0wLjExNjUgMC41NjAxLTAuMTc0OCAwLjg3MzgtMC4xNzQ4IDAuMzQ2NSAwIDAuNjQ5NyAwLjA1ODMgMC45MDk1IDAuMTc0OCAwLjI1OTkgMC4xMTY1IDAuNDc1IDAuMjgwOCAwLjY0NTMgMC40OTI4IDAuMTcyOSAwLjIwOTEgMC4zMDE5IDAuNDU4NiAwLjM4NDkgMC43NDgzIDAuMDg3IDAuMjg5OCAwLjEzIDAuNjA5NCAwLjEzIDAuOTU4OXYwLjQ2MTVoLTMuNzQ1NXYtMC43NzUyaDIuNjc5NHYtMC4wODUxYy0wLjAwNTktMC4xOTQyLTAuMDQ0OC0wLjM3NjQtMC4xMTY1LTAuNTQ2Ni0wLjA2ODctMC4xNzAzLTAuMTc0Ny0wLjMwNzctMC4zMTgxLTAuNDEyMy0wLjE0MzQtMC4xMDQ1LTAuMzM0NS0wLjE1NjgtMC41NzM1LTAuMTU2OC0wLjE3OTIgMC0wLjMzOTEgMC4wMzg4LTAuNDc5NSAwLjExNjUtMC4xMzc0IDAuMDc0Ny0wLjI1MjQgMC4xODM3LTAuMzQ1IDAuMzI3MXMtMC4xNjQzIDAuMzE2Ni0wLjIxNSAwLjUxOThjLTAuMDQ3OCAwLjIwMDEtMC4wNzE3IDAuNDI1Ni0wLjA3MTcgMC42NzY1djAuMTc5M2MwIDAuMjEyMSAwLjAyODMgMC40MDkyIDAuMDg1MSAwLjU5MTQgMC4wNTk3IDAuMTc5MyAwLjE0NjQgMC4zMzYxIDAuMjU5OSAwLjQ3MDVzMC4yNTA5IDAuMjQwNSAwLjQxMjIgMC4zMTgxYzAuMTYxMyAwLjA3NDcgMC4zNDUgMC4xMTIgMC41NTExIDAuMTEyIDAuMjU5OSAwIDAuNDkxNC0wLjA1MjIgMC42OTQ1LTAuMTU2OCAwLjIwMzItMC4xMDQ1IDAuMzc5NC0wLjI1MjQgMC41Mjg4LTAuNDQzNmwwLjU2ODggMC41NTEyYy0wLjEwNCAwLjE1MjMtMC4yNCAwLjI5ODctMC40MDc1IDAuNDM5MS0wLjE2NzMgMC4xMzc0LTAuMzcxOSAwLjI0OTQtMC42MTM5IDAuMzM2LTAuMjM5IDAuMDg2Ni0wLjUxNjggMC4xMy0wLjgzMzQgMC4xM3oiIGZpbGwtb3BhY2l0eT0iLjg3Ii8+CiAgIDxwYXRoIGQ9Im01MC4zNTYgMzYuNTk2djAuNjY4N2gtMi40NTY2di0wLjY2ODdoMi40NTY2em0tMi4yMjEzLTQuMjI0MnY0Ljg5MjloLTAuODQzNXYtNC44OTI5aDAuODQzNXptNC45ODU5IDQuMTYzN3YtMS43MzRjMC0wLjEzLTAuMDIzNi0wLjI0Mi0wLjA3MDYtMC4zMzYxLTAuMDQ3MS0wLjA5NDEtMC4xMTg3LTAuMTY2OS0wLjIxNTEtMC4yMTg0LTAuMDk0MS0wLjA1MTUtMC4yMTI4LTAuMDc3My0wLjM1NjItMC4wNzczLTAuMTMyMiAwLTAuMjQ2NCAwLjAyMjQtMC4zNDI4IDAuMDY3Mi0wLjA5NjMgMC4wNDQ4LTAuMTcxNCAwLjEwNTMtMC4yMjUxIDAuMTgxNS0wLjA1MzggMC4wNzYyLTAuMDgwNyAwLjE2MjQtMC4wODA3IDAuMjU4N2gtMC44MDY1YzAtMC4xNDMzIDAuMDM0Ny0wLjI4MjIgMC4xMDQyLTAuNDE2NyAwLjA2OTQtMC4xMzQ0IDAuMTcwMi0wLjI1NDIgMC4zMDI0LTAuMzU5NXMwLjI5MDEtMC4xODgyIDAuNDczOS0wLjI0ODdjMC4xODM3LTAuMDYwNSAwLjM4OTgtMC4wOTA3IDAuNjE4My0wLjA5MDcgMC4yNzMzIDAgMC41MTUzIDAuMDQ1OSAwLjcyNTkgMC4xMzc3IDAuMjEyOCAwLjA5MTkgMC4zNzk3IDAuMjMwOCAwLjUwMDcgMC40MTY3IDAuMTIzMiAwLjE4MzcgMC4xODQ4IDAuNDE0NSAwLjE4NDggMC42OTIzdjEuNjE2NGMwIDAuMTY1OCAwLjAxMTIgMC4zMTQ4IDAuMDMzNiAwLjQ0NyAwLjAyNDcgMC4xMjk5IDAuMDU5NCAwLjI0MyAwLjEwNDIgMC4zMzk0djAuMDUzN2gtMC44MzAxYy0wLjAzOC0wLjA4NzMtMC4wNjgzLTAuMTk4Mi0wLjA5MDctMC4zMzI2LTAuMDIwMi0wLjEzNjctMC4wMzAyLTAuMjY4OS0wLjAzMDItMC4zOTY2em0wLjExNzYtMS40ODIgMC4wMDY3IDAuNTAwN2gtMC41ODE0Yy0wLjE1MDEgMC0wLjI4MjMgMC4wMTQ2LTAuMzk2NSAwLjA0MzctMC4xMTQzIDAuMDI2OS0wLjIwOTUgMC4wNjcyLTAuMjg1NyAwLjEyMS0wLjA3NjEgMC4wNTM4LTAuMTMzMyAwLjExODctMC4xNzEzIDAuMTk0OS0wLjAzODEgMC4wNzYyLTAuMDU3MiAwLjE2MjQtMC4wNTcyIDAuMjU4OCAwIDAuMDk2MyAwLjAyMjQgMC4xODQ4IDAuMDY3MiAwLjI2NTUgMC4wNDQ4IDAuMDc4NCAwLjEwOTggMC4xNCAwLjE5NDkgMC4xODQ4IDAuMDg3NCAwLjA0NDggMC4xOTI3IDAuMDY3MiAwLjMxNTkgMC4wNjcyIDAuMTY1OCAwIDAuMzEwMy0wLjAzMzYgMC40MzM1LTAuMTAwOCAwLjEyNTUtMC4wNjk1IDAuMjI0MS0wLjE1MzUgMC4yOTU4LTAuMjUyMSAwLjA3MTctMC4xMDA4IDAuMTA5Ny0wLjE5NiAwLjExNDItMC4yODU2bDAuMjYyMiAwLjM1OTZjLTAuMDI2OSAwLjA5MTgtMC4wNzI5IDAuMTkwNC0wLjEzNzggMC4yOTU3LTAuMDY1IDAuMTA1My0wLjE1MDEgMC4yMDYxLTAuMjU1NCAwLjMwMjQtMC4xMDMxIDAuMDk0MS0wLjIyNzQgMC4xNzE0LTAuMzczIDAuMjMxOS0wLjE0MzQgMC4wNjA1LTAuMzA5MiAwLjA5MDgtMC40OTc0IDAuMDkwOC0wLjIzNzUgMC0wLjQ0OTItMC4wNDcxLTAuNjM1MS0wLjE0MTItMC4xODYtMC4wOTYzLTAuMzMxNi0wLjIyNTEtMC40MzY5LTAuMzg2NC0wLjEwNTMtMC4xNjM2LTAuMTU4LTAuMzQ4NC0wLjE1OC0wLjU1NDUgMC0wLjE5MjcgMC4wMzU5LTAuMzYzIDAuMTA3Ni0wLjUxMDggMC4wNzM5LTAuMTUwMSAwLjE4MTQtMC4yNzU2IDAuMzIyNi0wLjM3NjQgMC4xNDM0LTAuMTAwOCAwLjMxODEtMC4xNzcgMC41MjQyLTAuMjI4NSAwLjIwNjEtMC4wNTM4IDAuNDQxNC0wLjA4MDcgMC43MDU3LTAuMDgwN2gwLjYzNTJ6bTMuNzI1NyAxLjIyNjZjMC0wLjA4MDYtMC4wMjAyLTAuMTUzNC0wLjA2MDUtMC4yMTg0LTAuMDQwMy0wLjA2NzItMC4xMTc2LTAuMTI3Ny0wLjIzMTktMC4xODE1LTAuMTEyLTAuMDUzOC0wLjI3NzgtMC4xMDMtMC40OTczLTAuMTQ3OS0wLjE5MjctMC4wNDI1LTAuMzY5Ny0wLjA5MjktMC41MzEtMC4xNTEyLTAuMTU5MS0wLjA2MDUtMC4yOTU3LTAuMTMzMy0wLjQxLTAuMjE4NC0wLjExNDItMC4wODUxLTAuMjAyNy0wLjE4Ni0wLjI2NTUtMC4zMDI1LTAuMDYyNy0wLjExNjUtMC4wOTQxLTAuMjUwOS0wLjA5NDEtMC40MDMyIDAtMC4xNDc5IDAuMDMyNS0wLjI4NzkgMC4wOTc1LTAuNDIwMXMwLjE1NzktMC4yNDg3IDAuMjc4OS0wLjM0OTUgMC4yNjc3LTAuMTgwMyAwLjQ0MDItMC4yMzg2YzAuMTc0OC0wLjA1ODIgMC4zNjk3LTAuMDg3MyAwLjU4NDgtMC4wODczIDAuMzA0NyAwIDAuNTY1NyAwLjA1MTUgMC43ODMgMC4xNTQ1IDAuMjE5NSAwLjEwMDkgMC4zODc2IDAuMjM4NiAwLjUwNDEgMC40MTM0IDAuMTE2NSAwLjE3MjUgMC4xNzQ3IDAuMzY3NCAwLjE3NDcgMC41ODQ3aC0wLjgwOTljMC0wLjA5NjMtMC4wMjQ2LTAuMTg1OS0wLjA3MzktMC4yNjg4LTAuMDQ3MS0wLjA4NTItMC4xMTg4LTAuMTUzNS0wLjIxNTEtMC4yMDUtMC4wOTYzLTAuMDUzOC0wLjIxNzMtMC4wODA3LTAuMzYyOS0wLjA4MDctMC4xMzg5IDAtMC4yNTQzIDAuMDIyNC0wLjM0NjIgMC4wNjcyLTAuMDg5NiAwLjA0MjYtMC4xNTY4IDAuMDk4Ni0wLjIwMTYgMC4xNjgxLTAuMDQyNiAwLjA2OTQtMC4wNjM4IDAuMTQ1Ni0wLjA2MzggMC4yMjg1IDAgMC4wNjA1IDAuMDExMiAwLjExNTQgMC4wMzM2IDAuMTY0NiAwLjAyNDYgMC4wNDcxIDAuMDY0OSAwLjA5MDggMC4xMjA5IDAuMTMxMSAwLjA1NjEgMC4wMzgxIDAuMTMyMiAwLjA3MzkgMC4yMjg2IDAuMTA3NSAwLjA5ODUgMC4wMzM2IDAuMjIxOCAwLjA2NjEgMC4zNjk2IDAuMDk3NSAwLjI3NzggMC4wNTgyIDAuNTE2NCAwLjEzMzMgMC43MTU4IDAuMjI1MSAwLjIwMTYgMC4wODk3IDAuMzU2MiAwLjIwNjIgMC40NjM4IDAuMzQ5NSAwLjEwNzUgMC4xNDEyIDAuMTYxMyAwLjMyMDQgMC4xNjEzIDAuNTM3NyAwIDAuMTYxMy0wLjAzNDggMC4zMDkyLTAuMTA0MiAwLjQ0MzYtMC4wNjcyIDAuMTMyMi0wLjE2NTggMC4yNDc2LTAuMjk1NyAwLjM0NjItMC4xMyAwLjA5NjMtMC4yODU3IDAuMTcxMy0wLjQ2NzIgMC4yMjUxLTAuMTc5MiAwLjA1MzgtMC4zODA4IDAuMDgwNy0wLjYwNDggMC4wODA3LTAuMzI5NCAwLTAuNjA4My0wLjA1ODMtMC44MzY4LTAuMTc0OC0wLjIyODUtMC4xMTg3LTAuNDAyMi0wLjI3LTAuNTIwOS0wLjQ1MzctMC4xMTY1LTAuMTg1OS0wLjE3NDctMC4zNzg2LTAuMTc0Ny0wLjU3OGgwLjc4M2MwLjAwODkgMC4xNTAxIDAuMDUwNCAwLjI3IDAuMTI0MyAwLjM1OTYgMC4wNzYyIDAuMDg3NCAwLjE3MDMgMC4xNTEyIDAuMjgyMyAwLjE5MTYgMC4xMTQyIDAuMDM4IDAuMjMxOSAwLjA1NzEgMC4zNTI4IDAuMDU3MSAwLjE0NTcgMCAwLjI2NzgtMC4wMTkxIDAuMzY2My0wLjA1NzEgMC4wOTg2LTAuMDQwNCAwLjE3MzctMC4wOTQxIDAuMjI1Mi0wLjE2MTMgMC4wNTE1LTAuMDY5NSAwLjA3NzMtMC4xNDc5IDAuMDc3My0wLjIzNTN6bTMuMzEyMy0yLjY1MTR2MC41OTE0aC0yLjA0OTl2LTAuNTkxNGgyLjA0OTl6bS0xLjQ1ODQtMC44OTA2aDAuODA5OXYzLjUyMTljMCAwLjExMiAwLjAxNTYgMC4xOTgyIDAuMDQ3IDAuMjU4NyAwLjAzMzYgMC4wNTgzIDAuMDc5NSAwLjA5NzUgMC4xMzc4IDAuMTE3NiAwLjA1ODIgMC4wMjAyIDAuMTI2NiAwLjAzMDMgMC4yMDUgMC4wMzAzIDAuMDU2IDAgMC4xMDk4LTAuMDAzNCAwLjE2MTMtMC4wMTAxczAuMDkzLTAuMDEzNCAwLjEyNDMtMC4wMjAybDAuMDAzNCAwLjYxODRjLTAuMDY3MiAwLjAyMDEtMC4xNDU2IDAuMDM4MS0wLjIzNTMgMC4wNTM3LTAuMDg3MyAwLjAxNTctMC4xODgxIDAuMDIzNi0wLjMwMjQgMC4wMjM2LTAuMTg2IDAtMC4zNTA2LTAuMDMyNS0wLjQ5NC0wLjA5NzUtMC4xNDM0LTAuMDY3Mi0wLjI1NTQtMC4xNzU5LTAuMzM2MS0wLjMyNi0wLjA4MDYtMC4xNTAxLTAuMTIwOS0wLjM0OTUtMC4xMjA5LTAuNTk4MXYtMy41NzIzem02LjI3MTggMy42Njk3di0yLjc3OTFoMC44MTMzdjMuNjM2aC0wLjc2NjJsLTAuMDQ3MS0wLjg1Njl6bTAuMTE0My0wLjc1NjEgMC4yNzIyLTAuMDA2N2MwIDAuMjQ0Mi0wLjAyNjkgMC40NjkzLTAuMDgwNyAwLjY3NTQtMC4wNTM3IDAuMjAzOS0wLjEzNjYgMC4zODItMC4yNDg2IDAuNTM0NC0wLjExMjEgMC4xNTAxLTAuMjU1NCAwLjI2NzctMC40MzAyIDAuMzUyOC0wLjE3NDcgMC4wODI5LTAuMzg0MiAwLjEyNDQtMC42Mjg0IDAuMTI0NC0wLjE3NyAwLTAuMzM5NC0wLjAyNTgtMC40ODczLTAuMDc3My0wLjE0NzgtMC4wNTE2LTAuMjc1NS0wLjEzMTEtMC4zODMxLTAuMjM4Ni0wLjEwNTMtMC4xMDc2LTAuMTg3MS0wLjI0NzYtMC4yNDUzLTAuNDIwMS0wLjA1ODMtMC4xNzI1LTAuMDg3NC0wLjM3ODYtMC4wODc0LTAuNjE4M3YtMi4zNDloMC44MDk5djIuMzU1N2MwIDAuMTMyMiAwLjAxNTcgMC4yNDMxIDAuMDQ3MSAwLjMzMjcgMC4wMzEzIDAuMDg3NCAwLjA3MzkgMC4xNTc5IDAuMTI3NyAwLjIxMTcgMC4wNTM3IDAuMDUzOCAwLjExNjUgMC4wOTE4IDAuMTg4MSAwLjExNDMgMC4wNzE3IDAuMDIyNCAwLjE0NzkgMC4wMzM2IDAuMjI4NiAwLjAzMzYgMC4yMzA3IDAgMC40MTIyLTAuMDQ0OSAwLjU0NDQtMC4xMzQ1IDAuMTM0NC0wLjA5MTggMC4yMjk2LTAuMjE1IDAuMjg1Ni0wLjM2OTYgMC4wNTgzLTAuMTU0NiAwLjA4NzQtMC4zMjgyIDAuMDg3NC0wLjUyMDl6bTIuNDg1Ny0xLjMyNHY0LjMzNWgtMC44MDk5di01LjAzNGgwLjc0NmwwLjA2MzkgMC42OTl6bTIuMzY5MSAxLjA4NTR2MC4wNzA2YzAgMC4yNjQzLTAuMDMxMyAwLjUwOTctMC4wOTQxIDAuNzM1OS0wLjA2MDUgMC4yMjQxLTAuMTUxMiAwLjQyMDEtMC4yNzIyIDAuNTg4MS0wLjExODcgMC4xNjU4LTAuMjY1NSAwLjI5NDYtMC40NDAyIDAuMzg2NS0wLjE3NDggMC4wOTE4LTAuMzc2NCAwLjEzNzgtMC42MDQ5IDAuMTM3OC0wLjIyNjMgMC0wLjQyNDUtMC4wNDE1LTAuNTk0OC0wLjEyNDQtMC4xNjgtMC4wODUxLTAuMzEwMy0wLjIwNS0wLjQyNjgtMC4zNTk2LTAuMTE2NS0wLjE1NDUtMC4yMTA2LTAuMzM2LTAuMjgyMy0wLjU0NDQtMC4wNjk0LTAuMjEwNi0wLjExODctMC40NDEzLTAuMTQ3OC0wLjY5MjJ2LTAuMjcyMmMwLjAyOTEtMC4yNjY2IDAuMDc4NC0wLjUwODYgMC4xNDc4LTAuNzI1OSAwLjA3MTctMC4yMTczIDAuMTY1OC0wLjQwNDQgMC4yODIzLTAuNTYxMnMwLjI1ODgtMC4yNzc4IDAuNDI2OC0wLjM2MjljMC4xNjgtMC4wODUyIDAuMzY0LTAuMTI3NyAwLjU4ODEtMC4xMjc3IDAuMjI4NSAwIDAuNDMxMiAwLjA0NDggMC42MDgyIDAuMTM0NCAwLjE3NyAwLjA4NzMgMC4zMjYgMC4yMTI4IDAuNDQ3IDAuMzc2NCAwLjEyMSAwLjE2MTMgMC4yMTE3IDAuMzU2MiAwLjI3MjIgMC41ODQ3IDAuMDYwNSAwLjIyNjMgMC4wOTA3IDAuNDc4MyAwLjA5MDcgMC43NTYxem0tMC44MDk5IDAuMDcwNnYtMC4wNzA2YzAtMC4xNjgtMC4wMTU2LTAuMzIzNy0wLjA0Ny0wLjQ2NzEtMC4wMzE0LTAuMTQ1Ni0wLjA4MDctMC4yNzMzLTAuMTQ3OS0wLjM4MzFzLTAuMTUzNC0wLjE5NDktMC4yNTg3LTAuMjU1NGMtMC4xMDMxLTAuMDYyNy0wLjIyNzQtMC4wOTQxLTAuMzczMS0wLjA5NDEtMC4xNDMzIDAtMC4yNjY2IDAuMDI0Ni0wLjM2OTYgMC4wNzM5LTAuMTAzMSAwLjA0NzEtMC4xODkzIDAuMTEzMi0wLjI1ODggMC4xOTgzLTAuMDY5NCAwLjA4NTEtMC4xMjMyIDAuMTg0OC0wLjE2MTMgMC4yOTkxLTAuMDM4MSAwLjExMi0wLjA2NDkgMC4yMzQxLTAuMDgwNiAwLjM2NjN2MC42NTE5YzAuMDI2OSAwLjE2MTMgMC4wNzI4IDAuMzA5MiAwLjEzNzggMC40NDM2IDAuMDY0OSAwLjEzNDQgMC4xNTY4IDAuMjQyIDAuMjc1NSAwLjMyMjYgMC4xMjEgMC4wNzg0IDAuMjc1NiAwLjExNzYgMC40NjM4IDAuMTE3NiAwLjE0NTYgMCAwLjI2OTktMC4wMzEzIDAuMzczLTAuMDk0MSAwLjEwMy0wLjA2MjcgMC4xODcxLTAuMTQ4OSAwLjI1Mi0wLjI1ODcgMC4wNjcyLTAuMTEyIDAuMTE2NS0wLjI0MDkgMC4xNDc5LTAuMzg2NXMwLjA0Ny0wLjMwMDIgMC4wNDctMC40NjM3em0zLjg2MDIgMS4wMjgzdi00LjQwOWgwLjgxMzJ2NS4xNjE3aC0wLjczNTlsLTAuMDc3My0wLjc1Mjd6bS0yLjM2NTktMS4wMjV2LTAuMDcwNWMwLTAuMjc1NiAwLjAzMjUtMC41MjY1IDAuMDk3NS0wLjc1MjggMC4wNjUtMC4yMjg1IDAuMTU5MS0wLjQyNDUgMC4yODIzLTAuNTg4MSAwLjEyMzItMC4xNjU4IDAuMjczMy0wLjI5MjQgMC40NTAzLTAuMzc5NyAwLjE3Ny0wLjA4OTYgMC4zNzY0LTAuMTM0NCAwLjU5ODItMC4xMzQ0IDAuMjE5NSAwIDAuNDEyMiAwLjA0MjUgMC41NzggMC4xMjc3IDAuMTY1OCAwLjA4NTEgMC4zMDY5IDAuMjA3MiAwLjQyMzQgMC4zNjYyIDAuMTE2NSAwLjE1NjkgMC4yMDk1IDAuMzQ1MSAwLjI3ODkgMC41NjQ2IDAuMDY5NSAwLjIxNzMgMC4xMTg4IDAuNDU5MyAwLjE0NzkgMC43MjU5djAuMjI1MWMtMC4wMjkxIDAuMjU5OS0wLjA3ODQgMC40OTc0LTAuMTQ3OSAwLjcxMjUtMC4wNjk0IDAuMjE1LTAuMTYyNCAwLjQwMS0wLjI3ODkgMC41NTc4cy0wLjI1ODggMC4yNzc4LTAuNDI2OCAwLjM2M2MtMC4xNjU4IDAuMDg1MS0wLjM1OTYgMC4xMjc3LTAuNTgxMyAwLjEyNzctMC4yMTk2IDAtMC40MTc5LTAuMDQ2LTAuNTk0OS0wLjEzNzgtMC4xNzQ3LTAuMDkxOS0wLjMyMzctMC4yMjA3LTAuNDQ2OS0wLjM4NjVzLTAuMjE3My0wLjM2MDctMC4yODIzLTAuNTg0N2MtMC4wNjUtMC4yMjYzLTAuMDk3NS0wLjQ3MTYtMC4wOTc1LTAuNzM2em0wLjgwOTktMC4wNzA1djAuMDcwNWMwIDAuMTY1OCAwLjAxNDYgMC4zMjA0IDAuMDQzNyAwLjQ2MzggMC4wMzE0IDAuMTQzNCAwLjA3OTYgMC4yNjk5IDAuMTQ0NSAwLjM3OTcgMC4wNjUgMC4xMDc2IDAuMTQ5IDAuMTkyNyAwLjI1MjEgMC4yNTU0IDAuMTA1MyAwLjA2MDUgMC4yMzA3IDAuMDkwOCAwLjM3NjMgMC4wOTA4IDAuMTgzOCAwIDAuMzM1LTAuMDQwNCAwLjQ1MzctMC4xMjEgMC4xMTg4LTAuMDgwNyAwLjIxMTctMC4xODkzIDAuMjc4OS0wLjMyNiAwLjA2OTUtMC4xMzg5IDAuMTE2NS0wLjI5MzUgMC4xNDEyLTAuNDYzN3YtMC42MDgzYy0wLjAxMzUtMC4xMzIyLTAuMDQxNS0wLjI1NTQtMC4wODQtMC4zNjk3LTAuMDQwNC0wLjExNDItMC4wOTUyLTAuMjEzOS0wLjE2NDctMC4yOTktMC4wNjk1LTAuMDg3NC0wLjE1NTctMC4xNTQ2LTAuMjU4OC0wLjIwMTctMC4xMDA4LTAuMDQ5My0wLjIyMDYtMC4wNzM5LTAuMzU5NS0wLjA3MzktMC4xNDc5IDAtMC4yNzM0IDAuMDMxNC0wLjM3NjQgMC4wOTQxLTAuMTAzMSAwLjA2MjctMC4xODgyIDAuMTQ5LTAuMjU1NCAwLjI1ODctMC4wNjUgMC4xMDk4LTAuMTEzMiAwLjIzNzUtMC4xNDQ1IDAuMzgzMS0wLjAzMTQgMC4xNDU3LTAuMDQ3MSAwLjMwMTQtMC4wNDcxIDAuNDY3MnptNS40MDk0IDEuMTE5di0xLjczNGMwLTAuMTMtMC4wMjM2LTAuMjQyLTAuMDcwNi0wLjMzNjEtMC4wNDcxLTAuMDk0MS0wLjExODgtMC4xNjY5LTAuMjE1MS0wLjIxODQtMC4wOTQxLTAuMDUxNS0wLjIxMjgtMC4wNzczLTAuMzU2Mi0wLjA3NzMtMC4xMzIyIDAtMC4yNDY0IDAuMDIyNC0wLjM0MjggMC4wNjcyLTAuMDk2MyAwLjA0NDgtMC4xNzE0IDAuMTA1My0wLjIyNTEgMC4xODE1LTAuMDUzOCAwLjA3NjItMC4wODA3IDAuMTYyNC0wLjA4MDcgMC4yNTg3aC0wLjgwNjVjMC0wLjE0MzMgMC4wMzQ3LTAuMjgyMiAwLjEwNDItMC40MTY3IDAuMDY5NC0wLjEzNDQgMC4xNzAyLTAuMjU0MiAwLjMwMjQtMC4zNTk1czAuMjkwMS0wLjE4ODIgMC40NzM4LTAuMjQ4N2MwLjE4MzgtMC4wNjA1IDAuMzg5OS0wLjA5MDcgMC42MTg0LTAuMDkwNyAwLjI3MzMgMCAwLjUxNTMgMC4wNDU5IDAuNzI1OSAwLjEzNzcgMC4yMTI4IDAuMDkxOSAwLjM3OTcgMC4yMzA4IDAuNTAwNyAwLjQxNjcgMC4xMjMyIDAuMTgzNyAwLjE4NDggMC40MTQ1IDAuMTg0OCAwLjY5MjN2MS42MTY0YzAgMC4xNjU4IDAuMDExMiAwLjMxNDggMC4wMzM2IDAuNDQ3IDAuMDI0NyAwLjEyOTkgMC4wNTk0IDAuMjQzIDAuMTA0MiAwLjMzOTR2MC4wNTM3aC0wLjgzMDFjLTAuMDM4LTAuMDg3My0wLjA2ODMtMC4xOTgyLTAuMDkwNy0wLjMzMjYtMC4wMjAyLTAuMTM2Ny0wLjAzMDItMC4yNjg5LTAuMDMwMi0wLjM5NjZ6bTAuMTE3Ni0xLjQ4MiAwLjAwNjcgMC41MDA3aC0wLjU4MTRjLTAuMTUwMSAwLTAuMjgyMyAwLjAxNDYtMC4zOTY1IDAuMDQzNy0wLjExNDMgMC4wMjY5LTAuMjA5NSAwLjA2NzItMC4yODU3IDAuMTIxLTAuMDc2MSAwLjA1MzgtMC4xMzMzIDAuMTE4Ny0wLjE3MTMgMC4xOTQ5LTAuMDM4MSAwLjA3NjItMC4wNTcyIDAuMTYyNC0wLjA1NzIgMC4yNTg4IDAgMC4wOTYzIDAuMDIyNCAwLjE4NDggMC4wNjcyIDAuMjY1NSAwLjA0NDggMC4wNzg0IDAuMTA5OCAwLjE0IDAuMTk0OSAwLjE4NDggMC4wODc0IDAuMDQ0OCAwLjE5MjcgMC4wNjcyIDAuMzE1OSAwLjA2NzIgMC4xNjU4IDAgMC4zMTAzLTAuMDMzNiAwLjQzMzUtMC4xMDA4IDAuMTI1NS0wLjA2OTUgMC4yMjQxLTAuMTUzNSAwLjI5NTgtMC4yNTIxIDAuMDcxNy0wLjEwMDggMC4xMDk3LTAuMTk2IDAuMTE0Mi0wLjI4NTZsMC4yNjIxIDAuMzU5NmMtMC4wMjY4IDAuMDkxOC0wLjA3MjggMC4xOTA0LTAuMTM3NyAwLjI5NTctMC4wNjUgMC4xMDUzLTAuMTUwMSAwLjIwNjEtMC4yNTU0IDAuMzAyNC0wLjEwMzEgMC4wOTQxLTAuMjI3NCAwLjE3MTQtMC4zNzMxIDAuMjMxOS0wLjE0MzMgMC4wNjA1LTAuMzA5MSAwLjA5MDgtMC40OTczIDAuMDkwOC0wLjIzNzUgMC0wLjQ0OTItMC4wNDcxLTAuNjM1MS0wLjE0MTItMC4xODYtMC4wOTYzLTAuMzMxNi0wLjIyNTEtMC40MzY5LTAuMzg2NC0wLjEwNTMtMC4xNjM2LTAuMTU4LTAuMzQ4NC0wLjE1OC0wLjU1NDUgMC0wLjE5MjcgMC4wMzU5LTAuMzYzIDAuMTA3Ni0wLjUxMDggMC4wNzM5LTAuMTUwMSAwLjE4MTQtMC4yNzU2IDAuMzIyNi0wLjM3NjQgMC4xNDM0LTAuMTAwOCAwLjMxODEtMC4xNzcgMC41MjQyLTAuMjI4NSAwLjIwNjEtMC4wNTM4IDAuNDQxNC0wLjA4MDcgMC43MDU3LTAuMDgwN2gwLjYzNTJ6bTMuMzUyNy0xLjQyNDh2MC41OTE0aC0yLjA1di0wLjU5MTRoMi4wNXptLTEuNDU4NS0wLjg5MDZoMC44MDk5djMuNTIxOWMwIDAuMTEyIDAuMDE1NyAwLjE5ODIgMC4wNDcgMC4yNTg3IDAuMDMzNiAwLjA1ODMgMC4wNzk2IDAuMDk3NSAwLjEzNzggMC4xMTc2IDAuMDU4MyAwLjAyMDIgMC4xMjY2IDAuMDMwMyAwLjIwNSAwLjAzMDMgMC4wNTYgMCAwLjEwOTgtMC4wMDM0IDAuMTYxMy0wLjAxMDFzMC4wOTMtMC4wMTM0IDAuMTI0My0wLjAyMDJsMC4wMDM0IDAuNjE4NGMtMC4wNjcyIDAuMDIwMS0wLjE0NTYgMC4wMzgxLTAuMjM1MiAwLjA1MzctMC4wODc0IDAuMDE1Ny0wLjE4ODIgMC4wMjM2LTAuMzAyNSAwLjAyMzYtMC4xODU5IDAtMC4zNTA2LTAuMDMyNS0wLjQ5NC0wLjA5NzUtMC4xNDM0LTAuMDY3Mi0wLjI1NTQtMC4xNzU5LTAuMzM2LTAuMzI2LTAuMDgwNy0wLjE1MDEtMC4xMjEtMC4zNDk1LTAuMTIxLTAuNTk4MXYtMy41NzIzem0zLjgyOTkgNC41OTM5Yy0wLjI2ODkgMC0wLjUxMi0wLjA0MzctMC43MjkzLTAuMTMxMS0wLjIxNS0wLjA4OTYtMC4zOTg3LTAuMjE0LTAuNTUxMS0wLjM3My0wLjE1MDEtMC4xNTkxLTAuMjY1NS0wLjM0NjItMC4zNDYxLTAuNTYxMi0wLjA4MDctMC4yMTUxLTAuMTIxLTAuNDQ3LTAuMTIxLTAuNjk1N3YtMC4xMzQ0YzAtMC4yODQ1IDAuMDQxNC0wLjU0MjEgMC4xMjQzLTAuNzcyOXMwLjE5ODMtMC40Mjc5IDAuMzQ2Mi0wLjU5MTRjMC4xNDc4LTAuMTY1OCAwLjMyMjYtMC4yOTI0IDAuNTI0Mi0wLjM3OThzMC40MjAxLTAuMTMxIDAuNjU1My0wLjEzMWMwLjI1OTkgMCAwLjQ4NzMgMC4wNDM2IDAuNjgyMiAwLjEzMXMwLjM1NjIgMC4yMTA2IDAuNDgzOSAwLjM2OTdjMC4xMyAwLjE1NjggMC4yMjYzIDAuMzQzOSAwLjI4OSAwLjU2MTIgMC4wNjUgMC4yMTczIDAuMDk3NSAwLjQ1NyAwLjA5NzUgMC43MTkxdjAuMzQ2MmgtMi44MDk0di0wLjU4MTRoMi4wMDk2di0wLjA2MzljLTAuMDA0NS0wLjE0NTYtMC4wMzM2LTAuMjgyMi0wLjA4NzQtMC40MDk5LTAuMDUxNS0wLjEyNzctMC4xMzExLTAuMjMwOC0wLjIzODYtMC4zMDkycy0wLjI1MDktMC4xMTc2LTAuNDMwMS0wLjExNzZjLTAuMTM0NSAwLTAuMjU0MyAwLjAyOTEtMC4zNTk2IDAuMDg3My0wLjEwMzEgMC4wNTYxLTAuMTg5MyAwLjEzNzgtMC4yNTg4IDAuMjQ1NC0wLjA2OTQgMC4xMDc1LTAuMTIzMiAwLjIzNzQtMC4xNjEzIDAuMzg5OC0wLjAzNTggMC4xNTAxLTAuMDUzOCAwLjMxOTItMC4wNTM4IDAuNTA3NHYwLjEzNDRjMCAwLjE1OTEgMC4wMjEzIDAuMzA3IDAuMDYzOSAwLjQ0MzYgMC4wNDQ4IDAuMTM0NSAwLjEwOTggMC4yNTIxIDAuMTk0OSAwLjM1MjlzMC4xODgyIDAuMTgwMyAwLjMwOTIgMC4yMzg2YzAuMTIwOSAwLjA1NiAwLjI1ODcgMC4wODQgMC40MTMzIDAuMDg0IDAuMTk0OSAwIDAuMzY4Ni0wLjAzOTIgMC41MjA5LTAuMTE3NnMwLjI4NDUtMC4xODkzIDAuMzk2NS0wLjMzMjdsMC40MjY4IDAuNDEzM2MtMC4wNzg0IDAuMTE0My0wLjE4MDMgMC4yMjQxLTAuMzA1OCAwLjMyOTQtMC4xMjU0IDAuMTAzLTAuMjc4OSAwLjE4Ny0wLjQ2MDQgMC4yNTItMC4xNzkyIDAuMDY1LTAuMzg3NiAwLjA5NzUtMC42MjUgMC4wOTc1em02LjI1MTctNC45Nzd2NC45MDk3aC0wLjgwOTl2LTMuOTQ4NmwtMS4xOTk3IDAuNDA2N3YtMC42Njg4bDEuOTEyMS0wLjY5OWgwLjA5NzV6bTQuMTA4OCA0LjE1N3YtNC40MDloMC44MTMydjUuMTYxN2gtMC43MzU5bC0wLjA3NzMtMC43NTI3em0tMi4zNjU4LTEuMDI1di0wLjA3MDVjMC0wLjI3NTYgMC4wMzI0LTAuNTI2NSAwLjA5NzQtMC43NTI4IDAuMDY1LTAuMjI4NSAwLjE1OTEtMC40MjQ1IDAuMjgyMy0wLjU4ODEgMC4xMjMyLTAuMTY1OCAwLjI3MzMtMC4yOTI0IDAuNDUwMy0wLjM3OTcgMC4xNzctMC4wODk2IDAuMzc2NC0wLjEzNDQgMC41OTgyLTAuMTM0NCAwLjIxOTUgMCAwLjQxMjIgMC4wNDI1IDAuNTc4IDAuMTI3NyAwLjE2NTggMC4wODUxIDAuMzA2OSAwLjIwNzIgMC40MjM0IDAuMzY2MiAwLjExNjUgMC4xNTY5IDAuMjA5NSAwLjM0NTEgMC4yNzg5IDAuNTY0NiAwLjA2OTUgMC4yMTczIDAuMTE4OCAwLjQ1OTMgMC4xNDc5IDAuNzI1OXYwLjIyNTFjLTAuMDI5MSAwLjI1OTktMC4wNzg0IDAuNDk3NC0wLjE0NzkgMC43MTI1LTAuMDY5NCAwLjIxNS0wLjE2MjQgMC40MDEtMC4yNzg5IDAuNTU3OHMtMC4yNTg3IDAuMjc3OC0wLjQyNjggMC4zNjNjLTAuMTY1OCAwLjA4NTEtMC4zNTk1IDAuMTI3Ny0wLjU4MTMgMC4xMjc3LTAuMjE5NiAwLTAuNDE3OS0wLjA0Ni0wLjU5NDktMC4xMzc4LTAuMTc0Ny0wLjA5MTktMC4zMjM3LTAuMjIwNy0wLjQ0NjktMC4zODY1cy0wLjIxNzMtMC4zNjA3LTAuMjgyMy0wLjU4NDdjLTAuMDY1LTAuMjI2My0wLjA5NzQtMC40NzE2LTAuMDk3NC0wLjczNnptMC44MDk4LTAuMDcwNXYwLjA3MDVjMCAwLjE2NTggMC4wMTQ2IDAuMzIwNCAwLjA0MzcgMC40NjM4IDAuMDMxNCAwLjE0MzQgMC4wNzk2IDAuMjY5OSAwLjE0NDUgMC4zNzk3IDAuMDY1IDAuMTA3NiAwLjE0OSAwLjE5MjcgMC4yNTIxIDAuMjU1NCAwLjEwNTMgMC4wNjA1IDAuMjMwNyAwLjA5MDggMC4zNzYzIDAuMDkwOCAwLjE4MzggMCAwLjMzNS0wLjA0MDQgMC40NTM3LTAuMTIxIDAuMTE4OC0wLjA4MDcgMC4yMTE3LTAuMTg5MyAwLjI3ODktMC4zMjYgMC4wNjk1LTAuMTM4OSAwLjExNjUtMC4yOTM1IDAuMTQxMi0wLjQ2Mzd2LTAuNjA4M2MtMC4wMTM1LTAuMTMyMi0wLjA0MTUtMC4yNTU0LTAuMDg0LTAuMzY5Ny0wLjA0MDQtMC4xMTQyLTAuMDk1Mi0wLjIxMzktMC4xNjQ3LTAuMjk5LTAuMDY5NC0wLjA4NzQtMC4xNTU3LTAuMTU0Ni0wLjI1ODgtMC4yMDE3LTAuMTAwOC0wLjA0OTMtMC4yMjA2LTAuMDczOS0wLjM1OTUtMC4wNzM5LTAuMTQ3OSAwLTAuMjczNCAwLjAzMTQtMC4zNzY0IDAuMDk0MS0wLjEwMzEgMC4wNjI3LTAuMTg4MiAwLjE0OS0wLjI1NTQgMC4yNTg3LTAuMDY1IDAuMTA5OC0wLjExMzEgMC4yMzc1LTAuMTQ0NSAwLjM4MzEtMC4wMzE0IDAuMTQ1Ny0wLjA0NzEgMC4zMDE0LTAuMDQ3MSAwLjQ2NzJ6bTcuMjY2NiAxLjExOXYtMS43MzRjMC0wLjEzLTAuMDIzNS0wLjI0Mi0wLjA3MDYtMC4zMzYxLTAuMDQ3LTAuMDk0MS0wLjExODctMC4xNjY5LTAuMjE1LTAuMjE4NC0wLjA5NDEtMC4wNTE1LTAuMjEyOS0wLjA3NzMtMC4zNTYyLTAuMDc3My0wLjEzMjIgMC0wLjI0NjUgMC4wMjI0LTAuMzQyOCAwLjA2NzItMC4wOTY0IDAuMDQ0OC0wLjE3MTQgMC4xMDUzLTAuMjI1MiAwLjE4MTUtMC4wNTM3IDAuMDc2Mi0wLjA4MDYgMC4xNjI0LTAuMDgwNiAwLjI1ODdoLTAuODA2NmMwLTAuMTQzMyAwLjAzNDgtMC4yODIyIDAuMTA0Mi0wLjQxNjcgMC4wNjk1LTAuMTM0NCAwLjE3MDMtMC4yNTQyIDAuMzAyNS0wLjM1OTUgMC4xMzIxLTAuMTA1MyAwLjI5MDEtMC4xODgyIDAuNDczOC0wLjI0ODdzMC4zODk4LTAuMDkwNyAwLjYxODMtMC4wOTA3YzAuMjczNCAwIDAuNTE1MyAwLjA0NTkgMC43MjU5IDAuMTM3NyAwLjIxMjggMC4wOTE5IDAuMzc5OCAwLjIzMDggMC41MDA3IDAuNDE2NyAwLjEyMzIgMC4xODM3IDAuMTg0OSAwLjQxNDUgMC4xODQ5IDAuNjkyM3YxLjYxNjRjMCAwLjE2NTggMC4wMTEyIDAuMzE0OCAwLjAzMzYgMC40NDcgMC4wMjQ2IDAuMTI5OSAwLjA1OTMgMC4yNDMgMC4xMDQxIDAuMzM5NHYwLjA1MzdoLTAuODNjLTAuMDM4MS0wLjA4NzMtMC4wNjgzLTAuMTk4Mi0wLjA5MDctMC4zMzI2LTAuMDIwMi0wLjEzNjctMC4wMzAzLTAuMjY4OS0wLjAzMDMtMC4zOTY2em0wLjExNzYtMS40ODIgMC4wMDY4IDAuNTAwN2gtMC41ODE0Yy0wLjE1MDEgMC0wLjI4MjMgMC4wMTQ2LTAuMzk2NiAwLjA0MzctMC4xMTQyIDAuMDI2OS0wLjIwOTQgMC4wNjcyLTAuMjg1NiAwLjEyMXMtMC4xMzMzIDAuMTE4Ny0wLjE3MTQgMC4xOTQ5LTAuMDU3MSAwLjE2MjQtMC4wNTcxIDAuMjU4OGMwIDAuMDk2MyAwLjAyMjQgMC4xODQ4IDAuMDY3MiAwLjI2NTUgMC4wNDQ4IDAuMDc4NCAwLjEwOTggMC4xNCAwLjE5NDkgMC4xODQ4IDAuMDg3NCAwLjA0NDggMC4xOTI3IDAuMDY3MiAwLjMxNTkgMC4wNjcyIDAuMTY1OCAwIDAuMzEwMy0wLjAzMzYgMC40MzM1LTAuMTAwOCAwLjEyNTUtMC4wNjk1IDAuMjI0LTAuMTUzNSAwLjI5NTctMC4yNTIxIDAuMDcxNy0wLjEwMDggMC4xMDk4LTAuMTk2IDAuMTE0My0wLjI4NTZsMC4yNjIxIDAuMzU5NmMtMC4wMjY5IDAuMDkxOC0wLjA3MjggMC4xOTA0LTAuMTM3OCAwLjI5NTdzLTAuMTUwMSAwLjIwNjEtMC4yNTU0IDAuMzAyNGMtMC4xMDMgMC4wOTQxLTAuMjI3NCAwLjE3MTQtMC4zNzMgMC4yMzE5LTAuMTQzNCAwLjA2MDUtMC4zMDkyIDAuMDkwOC0wLjQ5NzQgMC4wOTA4LTAuMjM3NCAwLTAuNDQ5MS0wLjA0NzEtMC42MzUxLTAuMTQxMi0wLjE4NTktMC4wOTYzLTAuMzMxNi0wLjIyNTEtMC40MzY5LTAuMzg2NC0wLjEwNTMtMC4xNjM2LTAuMTU3OS0wLjM0ODQtMC4xNTc5LTAuNTU0NSAwLTAuMTkyNyAwLjAzNTgtMC4zNjMgMC4xMDc1LTAuNTEwOCAwLjA3NC0wLjE1MDEgMC4xODE1LTAuMjc1NiAwLjMyMjYtMC4zNzY0IDAuMTQzNC0wLjEwMDggMC4zMTgyLTAuMTc3IDAuNTI0My0wLjIyODUgMC4yMDYxLTAuMDUzOCAwLjQ0MTMtMC4wODA3IDAuNzA1Ny0wLjA4MDdoMC42MzUxem00LjAxNDktMS40MjQ4aDAuNzM2djMuNTM1MmMwIDAuMzI3MS0wLjA3IDAuNjA0OS0wLjIwOSAwLjgzMzQtMC4xMzggMC4yMjg2LTAuMzMyIDAuNDAyMi0wLjU4MSAwLjUyMDktMC4yNDkgMC4xMjEtMC41MzYgMC4xODE1LTAuODY0IDAuMTgxNS0wLjEzOCAwLTAuMjkzLTAuMDIwMi0wLjQ2My0wLjA2MDUtMC4xNjgtMC4wNDAzLTAuMzMyLTAuMTA1My0wLjQ5MS0wLjE5NDktMC4xNTctMC4wODc0LTAuMjg4LTAuMjAyOC0wLjM5My0wLjM0NjFsMC4zOC0wLjQ3NzJjMC4xMyAwLjE1NDUgMC4yNzMgMC4yNjc3IDAuNDMgMC4zMzk0czAuMzIxIDAuMTA3NSAwLjQ5NCAwLjEwNzVjMC4xODYgMCAwLjM0NC0wLjAzNDcgMC40NzQtMC4xMDQyIDAuMTMyLTAuMDY3MiAwLjIzNC0wLjE2NjkgMC4zMDUtMC4yOTkgMC4wNzItMC4xMzIyIDAuMTA4LTAuMjkzNSAwLjEwOC0wLjQ4NHYtMi43Mjg3bDAuMDc0LTAuODIzM3ptLTIuNDcgMS44NTgzdi0wLjA3MDVjMC0wLjI3NTYgMC4wMzMtMC41MjY1IDAuMTAxLTAuNzUyOCAwLjA2Ny0wLjIyODUgMC4xNjMtMC40MjQ1IDAuMjg5LTAuNTg4MSAwLjEyNS0wLjE2NTggMC4yNzctMC4yOTI0IDAuNDU3LTAuMzc5NyAwLjE3OS0wLjA4OTYgMC4zODItMC4xMzQ0IDAuNjA4LTAuMTM0NCAwLjIzNSAwIDAuNDM2IDAuMDQyNSAwLjYwMSAwLjEyNzcgMC4xNjkgMC4wODUxIDAuMzA5IDAuMjA3MiAwLjQyMSAwLjM2NjIgMC4xMTIgMC4xNTY5IDAuMTk5IDAuMzQ1MSAwLjI2MiAwLjU2NDYgMC4wNjUgMC4yMTczIDAuMTEzIDAuNDU5MyAwLjE0NCAwLjcyNTl2MC4yMjUxYy0wLjAyOSAwLjI1OTktMC4wNzggMC40OTc0LTAuMTQ4IDAuNzEyNS0wLjA2OSAwLjIxNS0wLjE2MSAwLjQwMS0wLjI3NSAwLjU1NzgtMC4xMTUgMC4xNTY4LTAuMjU2IDAuMjc3OC0wLjQyNCAwLjM2My0wLjE2NSAwLjA4NTEtMC4zNjEgMC4xMjc3LTAuNTg4IDAuMTI3Ny0wLjIyMiAwLTAuNDIyLTAuMDQ2LTAuNjAxLTAuMTM3OC0wLjE3Ny0wLjA5MTktMC4zMy0wLjIyMDctMC40NTctMC4zODY1LTAuMTI2LTAuMTY1OC0wLjIyMi0wLjM2MDctMC4yODktMC41ODQ3LTAuMDY4LTAuMjI2My0wLjEwMS0wLjQ3MTYtMC4xMDEtMC43MzZ6bTAuODEtMC4wNzA1djAuMDcwNWMwIDAuMTY1OCAwLjAxNSAwLjMyMDQgMC4wNDcgMC40NjM4IDAuMDMzIDAuMTQzNCAwLjA4NCAwLjI2OTkgMC4xNTEgMC4zNzk3IDAuMDY5IDAuMTA3NiAwLjE1NyAwLjE5MjcgMC4yNjIgMC4yNTU0IDAuMTA4IDAuMDYwNSAwLjIzNCAwLjA5MDggMC4zOCAwLjA5MDggMC4xOSAwIDAuMzQ2LTAuMDQwNCAwLjQ2Ny0wLjEyMSAwLjEyMy0wLjA4MDcgMC4yMTctMC4xODkzIDAuMjgyLTAuMzI2IDAuMDY3LTAuMTM4OSAwLjExNS0wLjI5MzUgMC4xNDEtMC40NjM3di0wLjYwODNjLTAuMDEzLTAuMTMyMi0wLjA0MS0wLjI1NTQtMC4wODQtMC4zNjk3LTAuMDQtMC4xMTQyLTAuMDk1LTAuMjEzOS0wLjE2NC0wLjI5OS0wLjA3LTAuMDg3NC0wLjE1Ny0wLjE1NDYtMC4yNjItMC4yMDE3LTAuMTA2LTAuMDQ5My0wLjIzLTAuMDczOS0wLjM3My0wLjA3MzktMC4xNDYgMC0wLjI3MyAwLjAzMTQtMC4zOCAwLjA5NDEtMC4xMDggMC4wNjI3LTAuMTk2IDAuMTQ5LTAuMjY2IDAuMjU4Ny0wLjA2NyAwLjEwOTgtMC4xMTcgMC4yMzc1LTAuMTUxIDAuMzgzMS0wLjAzMyAwLjE0NTctMC4wNSAwLjMwMTQtMC4wNSAwLjQ2NzJ6bTMuMjI1IDAuMDcwNXYtMC4wNzczYzAtMC4yNjIxIDAuMDM4LTAuNTA1MiAwLjExNC0wLjcyOTIgMC4wNzYtMC4yMjYzIDAuMTg2LTAuNDIyMyAwLjMyOS0wLjU4ODEgMC4xNDYtMC4xNjggMC4zMjMtMC4yOTggMC41MzEtMC4zODk4IDAuMjExLTAuMDk0MSAwLjQ0OC0wLjE0MTEgMC43MTMtMC4xNDExIDAuMjY2IDAgMC41MDQgMC4wNDcgMC43MTIgMC4xNDExIDAuMjExIDAuMDkxOCAwLjM4OSAwLjIyMTggMC41MzQgMC4zODk4IDAuMTQ2IDAuMTY1OCAwLjI1NyAwLjM2MTggMC4zMzMgMC41ODgxIDAuMDc2IDAuMjI0IDAuMTE0IDAuNDY3MSAwLjExNCAwLjcyOTJ2MC4wNzczYzAgMC4yNjIyLTAuMDM4IDAuNTA1Mi0wLjExNCAwLjcyOTMtMC4wNzYgMC4yMjQtMC4xODcgMC40Mi0wLjMzMyAwLjU4ODEtMC4xNDUgMC4xNjU3LTAuMzIyIDAuMjk1Ny0wLjUzMSAwLjM4OTgtMC4yMDggMC4wOTE4LTAuNDQ0IDAuMTM3OC0wLjcwOSAwLjEzNzgtMC4yNjYgMC0wLjUwNS0wLjA0Ni0wLjcxNS0wLjEzNzgtMC4yMDktMC4wOTQxLTAuMzg2LTAuMjI0MS0wLjUzMS0wLjM4OTgtMC4xNDYtMC4xNjgxLTAuMjU3LTAuMzY0MS0wLjMzMy0wLjU4ODEtMC4wNzYtMC4yMjQxLTAuMTE0LTAuNDY3MS0wLjExNC0wLjcyOTN6bTAuODEtMC4wNzczdjAuMDc3M2MwIDAuMTYzNiAwLjAxNiAwLjMxODIgMC4wNSAwLjQ2MzhzMC4wODYgMC4yNzMzIDAuMTU4IDAuMzgzMSAwLjE2NCAwLjE5NiAwLjI3NiAwLjI1ODdjMC4xMTIgMC4wNjI4IDAuMjQ1IDAuMDk0MSAwLjM5OSAwLjA5NDEgMC4xNTEgMCAwLjI4LTAuMDMxMyAwLjM5LTAuMDk0MSAwLjExMi0wLjA2MjcgMC4yMDQtMC4xNDg5IDAuMjc2LTAuMjU4N3MwLjEyNC0wLjIzNzUgMC4xNTgtMC4zODMxYzAuMDM2LTAuMTQ1NiAwLjA1NC0wLjMwMDIgMC4wNTQtMC40NjM4di0wLjA3NzNjMC0wLjE2MTMtMC4wMTgtMC4zMTM2LTAuMDU0LTAuNDU3LTAuMDM0LTAuMTQ1Ni0wLjA4OC0wLjI3NDQtMC4xNjItMC4zODY1LTAuMDcxLTAuMTEyLTAuMTYzLTAuMTk5My0wLjI3NS0wLjI2MjEtMC4xMS0wLjA2NDktMC4yNDEtMC4wOTc0LTAuMzkzLTAuMDk3NC0wLjE1MyAwLTAuMjg1IDAuMDMyNS0wLjM5NyAwLjA5NzQtMC4xMSAwLjA2MjgtMC4yIDAuMTUwMS0wLjI3MiAwLjI2MjEtMC4wNzIgMC4xMTIxLTAuMTI0IDAuMjQwOS0wLjE1OCAwLjM4NjUtMC4wMzQgMC4xNDM0LTAuMDUgMC4yOTU3LTAuMDUgMC40NTd6IiBmaWxsLW9wYWNpdHk9Ii4zOCIvPgogICA8cGF0aCBkPSJtNDguMTk2IDgwLjQ2OXYyLjc5NTloLTE0LjIxM3YtMi40MDI3bDYuOTAyNS03LjUyODdjMC43NTcyLTAuODU0MyAxLjM1NDMtMS41OTIyIDEuNzkxMS0yLjIxMzUgMC40MzY5LTAuNjIxMyAwLjc0MjctMS4xNzk1IDAuOTE3NS0xLjY3NDYgMC4xODQ0LTAuNTA0OSAwLjI3NjYtMC45OTUxIDAuMjc2Ni0xLjQ3MDggMC0wLjY2OTktMC4xMjYyLTEuMjU3Mi0wLjM3ODYtMS43NjIxLTAuMjQyNy0wLjUxNDUtMC42MDE5LTAuOTE3NC0xLjA3NzYtMS4yMDg2LTAuNDc1Ny0wLjMwMS0xLjA1MzMtMC40NTE1LTEuNzMyOS0wLjQ1MTUtMC43ODY0IDAtMS40NDY1IDAuMTY5OS0xLjk4MDUgMC41MDk3LTAuNTMzOSAwLjMzOTgtMC45MzY4IDAuODEwNi0xLjIwODYgMS40MTI2LTAuMjcxOSAwLjU5MjEtMC40MDc4IDEuMjcxNy0wLjQwNzggMi4wMzg3aC0zLjUwOTVjMC0xLjIzMyAwLjI4MTYtMi4zNTkxIDAuODQ0Ni0zLjM3ODUgMC41NjMxLTEuMDI5IDEuMzc4Ni0xLjg0NDUgMi40NDY1LTIuNDQ2NCAxLjA2NzktMC42MTE3IDIuMzU0Mi0wLjkxNzUgMy44NTktMC45MTc1IDEuNDE3NCAwIDIuNjIxMiAwLjIzNzkgMy42MTE0IDAuNzEzNiAwLjk5MDMgMC40NzU3IDEuNzQyNyAxLjE1MDQgMi4yNTcyIDIuMDI0MSAwLjUyNDIgMC44NzM4IDAuNzg2NCAxLjkwNzcgMC43ODY0IDMuMTAxOCAwIDAuNjYwMi0wLjEwNjggMS4zMTU1LTAuMzIwNCAxLjk2NTktMC4yMTM2IDAuNjUwNS0wLjUxOTQgMS4zMDA5LTAuOTE3NCAxLjk1MTQtMC4zODg0IDAuNjQwNy0wLjg0OTUgMS4yODYzLTEuMzgzNSAxLjkzNjctMC41MzM5IDAuNjQwOC0xLjEyMTIgMS4yOTEyLTEuNzYyIDEuOTUxNGwtNC41ODcxIDUuMDUzMWg5Ljc4NTh6bTE2LjQyOSAwdjIuNzk1OWgtMTQuMjEzdi0yLjQwMjdsNi45MDI2LTcuNTI4N2MwLjc1NzItMC44NTQzIDEuMzU0Mi0xLjU5MjIgMS43OTExLTIuMjEzNXMwLjc0MjctMS4xNzk1IDAuOTE3NC0xLjY3NDZjMC4xODQ1LTAuNTA0OSAwLjI3NjctMC45OTUxIDAuMjc2Ny0xLjQ3MDggMC0wLjY2OTktMC4xMjYyLTEuMjU3Mi0wLjM3ODYtMS43NjIxLTAuMjQyNy0wLjUxNDUtMC42MDE5LTAuOTE3NC0xLjA3NzYtMS4yMDg2LTAuNDc1Ny0wLjMwMS0xLjA1MzMtMC40NTE1LTEuNzMyOS0wLjQ1MTUtMC43ODY0IDAtMS40NDY1IDAuMTY5OS0xLjk4MDUgMC41MDk3LTAuNTMzOSAwLjMzOTgtMC45MzY4IDAuODEwNi0xLjIwODcgMS40MTI2LTAuMjcxOCAwLjU5MjEtMC40MDc3IDEuMjcxNy0wLjQwNzcgMi4wMzg3aC0zLjUwOTVjMC0xLjIzMyAwLjI4MTUtMi4zNTkxIDAuODQ0Ni0zLjM3ODUgMC41NjMxLTEuMDI5IDEuMzc4Ni0xLjg0NDUgMi40NDY1LTIuNDQ2NCAxLjA2NzktMC42MTE3IDIuMzU0Mi0wLjkxNzUgMy44NTktMC45MTc1IDEuNDE3NCAwIDIuNjIxMiAwLjIzNzkgMy42MTE0IDAuNzEzNnMxLjc0MjYgMS4xNTA0IDIuMjU3MiAyLjAyNDFjMC41MjQyIDAuODczOCAwLjc4NjMgMS45MDc3IDAuNzg2MyAzLjEwMTggMCAwLjY2MDItMC4xMDY4IDEuMzE1NS0wLjMyMDMgMS45NjU5LTAuMjEzNiAwLjY1MDUtMC41MTk0IDEuMzAwOS0wLjkxNzUgMS45NTE0LTAuMzg4MyAwLjY0MDctMC44NDk0IDEuMjg2My0xLjM4MzQgMS45MzY3LTAuNTMzOSAwLjY0MDgtMS4xMjEzIDEuMjkxMi0xLjc2MiAxLjk1MTRsLTQuNTg3MSA1LjA1MzFoOS43ODU4em0yLjQ5MjUtMTQuODFjMC0wLjcwODcgMC4xNzQ3LTEuMzU5MiAwLjUyNDItMS45NTE0czAuODE1NS0xLjA2MyAxLjM5OC0xLjQxMjVjMC41OTIyLTAuMzU5MiAxLjIzMjktMC41Mzg4IDEuOTIyMi0wLjUzODggMC42OTkgMCAxLjMzNDkgMC4xNzk2IDEuOTA3NyAwLjUzODggMC41NzI4IDAuMzQ5NSAxLjAyOTEgMC44MjAzIDEuMzY4OCAxLjQxMjUgMC4zNDk1IDAuNTkyMiAwLjUyNDMgMS4yNDI3IDAuNTI0MyAxLjk1MTRzLTAuMTc0OCAxLjM1OTEtMC41MjQzIDEuOTUxM2MtMC4zMzk3IDAuNTgyNS0wLjc5NiAxLjA0MzYtMS4zNjg4IDEuMzgzNHMtMS4yMDg3IDAuNTA5Ny0xLjkwNzcgMC41MDk3Yy0wLjY4OTMgMC0xLjMzLTAuMTY5OS0xLjkyMjItMC41MDk3LTAuNTgyNS0wLjMzOTgtMS4wNDg1LTAuODAwOS0xLjM5OC0xLjM4MzQtMC4zNDk1LTAuNTkyMi0wLjUyNDItMS4yNDI2LTAuNTI0Mi0xLjk1MTN6bTEuOTY1OSAwYzAgMC41MjQyIDAuMTg0NSAwLjk2NTkgMC41NTM0IDEuMzI1MSAwLjM2ODkgMC4zNDk1IDAuODEwNiAwLjUyNDMgMS4zMjUxIDAuNTI0MyAwLjUxNDYgMCAwLjk0NjYtMC4xNzQ4IDEuMjk2MS0wLjUyNDNzMC41MjQyLTAuNzkxMiAwLjUyNDItMS4zMjUxYzAtMC41NDM3LTAuMTc0Ny0wLjk5NTEtMC41MjQyLTEuMzU0M3MtMC43ODE1LTAuNTM4OC0xLjI5NjEtMC41Mzg4Yy0wLjUxNDUgMC0wLjk1NjIgMC4xNzk2LTEuMzI1MSAwLjUzODhzLTAuNTUzNCAwLjgxMDYtMC41NTM0IDEuMzU0M3ptMjEuNzI5IDEwLjcwM2gzLjY0MDZjLTAuMTE2NSAxLjM4ODMtMC41MDQ4IDIuNjI2MS0xLjE2NSAzLjcxMzQtMC42NjAxIDEuMDc3Ni0xLjU4NzMgMS45MjcxLTIuNzgxNCAyLjU0ODRzLTIuNjQ1NCAwLjkzMi00LjM1NDEgMC45MzJjLTEuMzEwNiAwLTIuNDkwMS0wLjIzMy0zLjUzODYtMC42OTktMS4wNDg1LTAuNDc1Ny0xLjk0NjUtMS4xNDU2LTIuNjk0LTIuMDA5Ni0wLjc0NzYtMC44NzM3LTEuMzIwNC0xLjkyNzEtMS43MTg0LTMuMTYtMC4zODgzLTEuMjMyOS0wLjU4MjUtMi42MTE1LTAuNTgyNS00LjEzNTd2LTEuNzYyYzAtMS41MjQyIDAuMTk5LTIuOTAyOCAwLjU5NzEtNC4xMzU3IDAuNDA3Ny0xLjIzMjkgMC45OTAyLTIuMjg2MyAxLjc0NzQtMy4xNiAwLjc1NzMtMC44ODM1IDEuNjY1LTEuNTU4MiAyLjcyMzItMi4wMjQyIDEuMDY3OS0wLjQ2NiAyLjI2NjktMC42OTkgMy41OTY5LTAuNjk5IDEuNjg5MiAwIDMuMTE2MyAwLjMxMDcgNC4yODEzIDAuOTMyczIuMDY3OCAxLjQ4MDUgMi43MDg2IDIuNTc3NWMwLjY1MDQgMS4wOTcxIDEuMDQ4NCAyLjM1NDMgMS4xOTQxIDMuNzcxN2gtMy42NDA2Yy0wLjA5NzEtMC45MTI2LTAuMzEwNy0xLjY5NDEtMC42NDA3LTIuMzQ0Ni0wLjMyMDQtMC42NTA0LTAuNzk2MS0xLjE0NTUtMS40MjcxLTEuNDg1My0wLjYzMTEtMC4zNDk1LTEuNDU2My0wLjUyNDItMi40NzU2LTAuNTI0Mi0wLjgzNDkgMC0xLjU2MyAwLjE1NTMtMi4xODQ0IDAuNDY1OS0wLjYyMTMgMC4zMTA3LTEuMTQwNyAwLjc2Ny0xLjU1ODEgMS4zNjg5LTAuNDE3NSAwLjYwMTktMC43MzMgMS4zNDQ2LTAuOTQ2NiAyLjIyOC0wLjIwMzkgMC44NzM4LTAuMzA1OCAxLjg3MzctMC4zMDU4IDIuOTk5OXYxLjc5MTFjMCAxLjA2NzkgMC4wOTIyIDIuMDM4NyAwLjI3NjcgMi45MTI1IDAuMTk0MiAwLjg2NCAwLjQ4NTQgMS42MDY3IDAuODczNyAyLjIyOCAwLjM5ODEgMC42MjEzIDAuOTAyOSAxLjEwMTkgMS41MTQ1IDEuNDQxNyAwLjYxMTYgMC4zMzk3IDEuMzQ0NiAwLjUwOTYgMi4xOTg5IDAuNTA5NiAxLjAzODggMCAxLjg3ODUtMC4xNjUgMi41MTkzLTAuNDk1MSAwLjY1MDQtMC4zMzAxIDEuMTQwNy0wLjgxMDYgMS40NzA4LTEuNDQxNiAwLjMzOTgtMC42NDA4IDAuNTYzLTEuNDIyMyAwLjY2OTgtMi4zNDQ2eiIgZmlsbC1vcGFjaXR5PSIuODciLz4KICA8L2c+CiA8L2c+CiA8ZGVmcz4KICA8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfMTE0Ml8yMDM5NTQiIHg9Ii45MTE3NiIgeT0iLjIwNTg4IiB3aWR0aD0iMTI2LjE4IiBoZWlnaHQ9IjEyNi4xOCIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICA8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgogICA8ZmVDb2xvck1hdHJpeCBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0iaGFyZEFscGhhIiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIi8+CiAgIDxmZU9mZnNldCBkeT0iMi4yOTQxMiIvPgogICA8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyLjI5NDEyIi8+CiAgIDxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgogICA8ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDQgMCIvPgogICA8ZmVCbGVuZCBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfMTE0Ml8yMDM5NTQiLz4KICAgPGZlQmxlbmQgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9kcm9wU2hhZG93XzExNDJfMjAzOTU0IiByZXN1bHQ9InNoYXBlIi8+CiAgPC9maWx0ZXI+CiA8L2RlZnM+Cjwvc3ZnPgo=", "description": "Designed to display single value of the selected attribute or timeseries data. Widget styles are customizable.", "descriptor": { "type": "latest", - "sizeX": 2.5, - "sizeY": 2.5, + "sizeX": 3, + "sizeY": 3, "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '250px',\n previewHeight: '250px'\n };\n};\n\nself.onDestroy = function() {\n};\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '250px',\n previewHeight: '250px',\n absoluteHeader: true\n };\n};\n\nself.onDestroy = function() {\n};\n", "settingsSchema": "", "dataKeySettingsSchema": "", - "settingsDirective": "", + "settingsDirective": "tb-value-card-widget-settings", "hasBasicMode": true, "basicModeDirective": "tb-value-card-basic-config", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"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}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}" @@ -250,7 +250,7 @@ { "alias": "horizontal_value_card", "name": "Horizontal value card", - "image": null, + "image": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzk5IiBoZWlnaHQ9IjEwOCIgdmlld0JveD0iMCAwIDM5OSAxMDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfMTI0Nl80NDQ0NykiPgo8cmVjdCB4PSI4IiB5PSI0IiB3aWR0aD0iMzgzIiBoZWlnaHQ9IjkyIiByeD0iNCIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTU3LjAwMDEgNTEuNjY2N1YzOC4zMzM0QzU3LjAwMDEgMzUuNTY2NyA1NC43NjY3IDMzLjMzMzQgNTIuMDAwMSAzMy4zMzM0QzQ5LjIzMzQgMzMuMzMzNCA0Ny4wMDAxIDM1LjU2NjcgNDcuMDAwMSAzOC4zMzM0VjUxLjY2NjdDNDQuOTgzNCA1My4xODM0IDQzLjY2NjcgNTUuNjE2NyA0My42NjY3IDU4LjMzMzRDNDMuNjY2NyA2Mi45MzM0IDQ3LjQwMDEgNjYuNjY2NyA1Mi4wMDAxIDY2LjY2NjdDNTYuNjAwMSA2Ni42NjY3IDYwLjMzMzQgNjIuOTMzNCA2MC4zMzM0IDU4LjMzMzRDNjAuMzMzNCA1NS42MTY3IDU5LjAxNjcgNTMuMTgzNCA1Ny4wMDAxIDUxLjY2NjdaTTUwLjMzMzQgMzguMzMzNEM1MC4zMzM0IDM3LjQxNjcgNTEuMDgzNCAzNi42NjY3IDUyLjAwMDEgMzYuNjY2N0M1Mi45MTY3IDM2LjY2NjcgNTMuNjY2NyAzNy40MTY3IDUzLjY2NjcgMzguMzMzNEg1Mi4wMDAxVjQwSDUzLjY2NjdWNDMuMzMzNEg1Mi4wMDAxVjQ1SDUzLjY2NjdWNDguMzMzNEg1MC4zMzM0VjM4LjMzMzRaIiBmaWxsPSIjNTQ2OUZGIi8+CjxwYXRoIGQ9Ik04NS44MzU5IDM1LjYyNVY0N0g4My44OTA2VjM1LjYyNUg4NS44MzU5Wk04OS40MDYyIDM1LjYyNVYzNy4xODc1SDgwLjM1MTZWMzUuNjI1SDg5LjQwNjJaTTkzLjk0NTMgNDcuMTU2MkM5My4zMjAzIDQ3LjE1NjIgOTIuNzU1MiA0Ny4wNTQ3IDkyLjI1IDQ2Ljg1MTZDOTEuNzUgNDYuNjQzMiA5MS4zMjI5IDQ2LjM1NDIgOTAuOTY4OCA0NS45ODQ0QzkwLjYxOTggNDUuNjE0NiA5MC4zNTE2IDQ1LjE3OTcgOTAuMTY0MSA0NC42Nzk3Qzg5Ljk3NjYgNDQuMTc5NyA4OS44ODI4IDQzLjY0MDYgODkuODgyOCA0My4wNjI1VjQyLjc1Qzg5Ljg4MjggNDIuMDg4NSA4OS45NzkyIDQxLjQ4OTYgOTAuMTcxOSA0MC45NTMxQzkwLjM2NDYgNDAuNDE2NyA5MC42MzI4IDM5Ljk1ODMgOTAuOTc2NiAzOS41NzgxQzkxLjMyMDMgMzkuMTkyNyA5MS43MjY2IDM4Ljg5ODQgOTIuMTk1MyAzOC42OTUzQzkyLjY2NDEgMzguNDkyMiA5My4xNzE5IDM4LjM5MDYgOTMuNzE4OCAzOC4zOTA2Qzk0LjMyMjkgMzguMzkwNiA5NC44NTE2IDM4LjQ5MjIgOTUuMzA0NyAzOC42OTUzQzk1Ljc1NzggMzguODk4NCA5Ni4xMzI4IDM5LjE4NDkgOTYuNDI5NyAzOS41NTQ3Qzk2LjczMTggMzkuOTE5MyA5Ni45NTU3IDQwLjM1NDIgOTcuMTAxNiA0MC44NTk0Qzk3LjI1MjYgNDEuMzY0NiA5Ny4zMjgxIDQxLjkyMTkgOTcuMzI4MSA0Mi41MzEyVjQzLjMzNTlIOTAuNzk2OVY0MS45ODQ0SDk1LjQ2ODhWNDEuODM1OUM5NS40NTgzIDQxLjQ5NzQgOTUuMzkwNiA0MS4xNzk3IDk1LjI2NTYgNDAuODgyOEM5NS4xNDU4IDQwLjU4NTkgOTQuOTYwOSA0MC4zNDY0IDk0LjcxMDkgNDAuMTY0MUM5NC40NjA5IDM5Ljk4MTggOTQuMTI3NiAzOS44OTA2IDkzLjcxMDkgMzkuODkwNkM5My4zOTg0IDM5Ljg5MDYgOTMuMTE5OCAzOS45NTgzIDkyLjg3NSA0MC4wOTM4QzkyLjYzNTQgNDAuMjI0IDkyLjQzNDkgNDAuNDE0MSA5Mi4yNzM0IDQwLjY2NDFDOTIuMTEyIDQwLjkxNDEgOTEuOTg3IDQxLjIxNjEgOTEuODk4NCA0MS41NzAzQzkxLjgxNTEgNDEuOTE5MyA5MS43NzM0IDQyLjMxMjUgOTEuNzczNCA0Mi43NVY0My4wNjI1QzkxLjc3MzQgNDMuNDMyMyA5MS44MjI5IDQzLjc3NiA5MS45MjE5IDQ0LjA5MzhDOTIuMDI2IDQ0LjQwNjIgOTIuMTc3MSA0NC42Nzk3IDkyLjM3NSA0NC45MTQxQzkyLjU3MjkgNDUuMTQ4NCA5Mi44MTI1IDQ1LjMzMzMgOTMuMDkzOCA0NS40Njg4QzkzLjM3NSA0NS41OTkgOTMuNjk1MyA0NS42NjQxIDk0LjA1NDcgNDUuNjY0MUM5NC41MDc4IDQ1LjY2NDEgOTQuOTExNSA0NS41NzI5IDk1LjI2NTYgNDUuMzkwNkM5NS42MTk4IDQ1LjIwODMgOTUuOTI3MSA0NC45NTA1IDk2LjE4NzUgNDQuNjE3Mkw5Ny4xNzk3IDQ1LjU3ODFDOTYuOTk3NCA0NS44NDM4IDk2Ljc2MDQgNDYuMDk5IDk2LjQ2ODggNDYuMzQzOEM5Ni4xNzcxIDQ2LjU4MzMgOTUuODIwMyA0Ni43Nzg2IDk1LjM5ODQgNDYuOTI5N0M5NC45ODE4IDQ3LjA4MDcgOTQuNDk3NCA0Ny4xNTYyIDkzLjk0NTMgNDcuMTU2MlpNMTAwLjkzIDQwLjI2NTZWNDdIOTkuMDQ2OVYzOC41NDY5SDEwMC44MkwxMDAuOTMgNDAuMjY1NlpNMTAwLjYyNSA0Mi40NjA5TDk5Ljk4NDQgNDIuNDUzMUM5OS45ODQ0IDQxLjg2OTggMTAwLjA1NyA0MS4zMzA3IDEwMC4yMDMgNDAuODM1OUMxMDAuMzQ5IDQwLjM0MTEgMTAwLjU2MiAzOS45MTE1IDEwMC44NDQgMzkuNTQ2OUMxMDEuMTI1IDM5LjE3NzEgMTAxLjQ3NCAzOC44OTMyIDEwMS44OTEgMzguNjk1M0MxMDIuMzEyIDM4LjQ5MjIgMTAyLjc5OSAzOC4zOTA2IDEwMy4zNTIgMzguMzkwNkMxMDMuNzM3IDM4LjM5MDYgMTA0LjA4OSAzOC40NDc5IDEwNC40MDYgMzguNTYyNUMxMDQuNzI5IDM4LjY3MTkgMTA1LjAwOCAzOC44NDY0IDEwNS4yNDIgMzkuMDg1OUMxMDUuNDgyIDM5LjMyNTUgMTA1LjY2NCAzOS42MzI4IDEwNS43ODkgNDAuMDA3OEMxMDUuOTE5IDQwLjM4MjggMTA1Ljk4NCA0MC44MzU5IDEwNS45ODQgNDEuMzY3MlY0N0gxMDQuMTAyVjQxLjUzMTJDMTA0LjEwMiA0MS4xMTk4IDEwNC4wMzkgNDAuNzk2OSAxMDMuOTE0IDQwLjU2MjVDMTAzLjc5NCA0MC4zMjgxIDEwMy42MiA0MC4xNjE1IDEwMy4zOTEgNDAuMDYyNUMxMDMuMTY3IDM5Ljk1ODMgMTAyLjg5OCAzOS45MDYyIDEwMi41ODYgMzkuOTA2MkMxMDIuMjMyIDM5LjkwNjIgMTAxLjkzIDM5Ljk3NCAxMDEuNjggNDAuMTA5NEMxMDEuNDM1IDQwLjI0NDggMTAxLjIzNCA0MC40Mjk3IDEwMS4wNzggNDAuNjY0MUMxMDAuOTIyIDQwLjg5ODQgMTAwLjgwNyA0MS4xNjkzIDEwMC43MzQgNDEuNDc2NkMxMDAuNjYxIDQxLjc4MzkgMTAwLjYyNSA0Mi4xMTIgMTAwLjYyNSA0Mi40NjA5Wk0xMDUuODY3IDQxLjk2MDlMMTA0Ljk4NCA0Mi4xNTYyQzEwNC45ODQgNDEuNjQ1OCAxMDUuMDU1IDQxLjE2NDEgMTA1LjE5NSA0MC43MTA5QzEwNS4zNDEgNDAuMjUyNiAxMDUuNTUyIDM5Ljg1MTYgMTA1LjgyOCAzOS41MDc4QzEwNi4xMDkgMzkuMTU4OSAxMDYuNDU2IDM4Ljg4NTQgMTA2Ljg2NyAzOC42ODc1QzEwNy4yNzkgMzguNDg5NiAxMDcuNzUgMzguMzkwNiAxMDguMjgxIDM4LjM5MDZDMTA4LjcxNCAzOC4zOTA2IDEwOS4wOTkgMzguNDUwNSAxMDkuNDM4IDM4LjU3MDNDMTA5Ljc4MSAzOC42ODQ5IDExMC4wNzMgMzguODY3MiAxMTAuMzEyIDM5LjExNzJDMTEwLjU1MiAzOS4zNjcyIDExMC43MzQgMzkuNjkyNyAxMTAuODU5IDQwLjA5MzhDMTEwLjk4NCA0MC40ODk2IDExMS4wNDcgNDAuOTY4OCAxMTEuMDQ3IDQxLjUzMTJWNDdIMTA5LjE1NlY0MS41MjM0QzEwOS4xNTYgNDEuMDk2NCAxMDkuMDk0IDQwLjc2NTYgMTA4Ljk2OSA0MC41MzEyQzEwOC44NDkgNDAuMjk2OSAxMDguNjc3IDQwLjEzNTQgMTA4LjQ1MyA0MC4wNDY5QzEwOC4yMjkgMzkuOTUzMSAxMDcuOTYxIDM5LjkwNjIgMTA3LjY0OCAzOS45MDYyQzEwNy4zNTcgMzkuOTA2MiAxMDcuMDk5IDM5Ljk2MDkgMTA2Ljg3NSA0MC4wNzAzQzEwNi42NTYgNDAuMTc0NSAxMDYuNDcxIDQwLjMyMjkgMTA2LjMyIDQwLjUxNTZDMTA2LjE2OSA0MC43MDMxIDEwNi4wNTUgNDAuOTE5MyAxMDUuOTc3IDQxLjE2NDFDMTA1LjkwNCA0MS40MDg5IDEwNS44NjcgNDEuNjc0NSAxMDUuODY3IDQxLjk2MDlaTTExNS4xMjUgNDAuMTcxOVY1MC4yNUgxMTMuMjQyVjM4LjU0NjlIMTE0Ljk3N0wxMTUuMTI1IDQwLjE3MTlaTTEyMC42MzMgNDIuNjk1M1Y0Mi44NTk0QzEyMC42MzMgNDMuNDc0IDEyMC41NiA0NC4wNDQzIDEyMC40MTQgNDQuNTcwM0MxMjAuMjczIDQ1LjA5MTEgMTIwLjA2MiA0NS41NDY5IDExOS43ODEgNDUuOTM3NUMxMTkuNTA1IDQ2LjMyMjkgMTE5LjE2NCA0Ni42MjI0IDExOC43NTggNDYuODM1OUMxMTguMzUyIDQ3LjA0OTUgMTE3Ljg4MyA0Ny4xNTYyIDExNy4zNTIgNDcuMTU2MkMxMTYuODI2IDQ3LjE1NjIgMTE2LjM2NSA0Ny4wNTk5IDExNS45NjkgNDYuODY3MkMxMTUuNTc4IDQ2LjY2OTMgMTE1LjI0NyA0Ni4zOTA2IDExNC45NzcgNDYuMDMxMkMxMTQuNzA2IDQ1LjY3MTkgMTE0LjQ4NyA0NS4yNSAxMTQuMzIgNDQuNzY1NkMxMTQuMTU5IDQ0LjI3NiAxMTQuMDQ0IDQzLjczOTYgMTEzLjk3NyA0My4xNTYyVjQyLjUyMzRDMTE0LjA0NCA0MS45MDM2IDExNC4xNTkgNDEuMzQxMSAxMTQuMzIgNDAuODM1OUMxMTQuNDg3IDQwLjMzMDcgMTE0LjcwNiAzOS44OTU4IDExNC45NzcgMzkuNTMxMkMxMTUuMjQ3IDM5LjE2NjcgMTE1LjU3OCAzOC44ODU0IDExNS45NjkgMzguNjg3NUMxMTYuMzU5IDM4LjQ4OTYgMTE2LjgxNSAzOC4zOTA2IDExNy4zMzYgMzguMzkwNkMxMTcuODY3IDM4LjM5MDYgMTE4LjMzOSAzOC40OTQ4IDExOC43NSAzOC43MDMxQzExOS4xNjEgMzguOTA2MiAxMTkuNTA4IDM5LjE5NzkgMTE5Ljc4OSAzOS41NzgxQzEyMC4wNyAzOS45NTMxIDEyMC4yODEgNDAuNDA2MiAxMjAuNDIyIDQwLjkzNzVDMTIwLjU2MiA0MS40NjM1IDEyMC42MzMgNDIuMDQ5NSAxMjAuNjMzIDQyLjY5NTNaTTExOC43NSA0Mi44NTk0VjQyLjY5NTNDMTE4Ljc1IDQyLjMwNDcgMTE4LjcxNCA0MS45NDI3IDExOC42NDEgNDEuNjA5NEMxMTguNTY4IDQxLjI3MDggMTE4LjQ1MyA0MC45NzQgMTE4LjI5NyA0MC43MTg4QzExOC4xNDEgNDAuNDYzNSAxMTcuOTQgNDAuMjY1NiAxMTcuNjk1IDQwLjEyNUMxMTcuNDU2IDM5Ljk3OTIgMTE3LjE2NyAzOS45MDYyIDExNi44MjggMzkuOTA2MkMxMTYuNDk1IDM5LjkwNjIgMTE2LjIwOCAzOS45NjM1IDExNS45NjkgNDAuMDc4MUMxMTUuNzI5IDQwLjE4NzUgMTE1LjUyOSA0MC4zNDExIDExNS4zNjcgNDAuNTM5MUMxMTUuMjA2IDQwLjczNyAxMTUuMDgxIDQwLjk2ODggMTE0Ljk5MiA0MS4yMzQ0QzExNC45MDQgNDEuNDk0OCAxMTQuODQxIDQxLjc3ODYgMTE0LjgwNSA0Mi4wODU5VjQzLjYwMTZDMTE0Ljg2NyA0My45NzY2IDExNC45NzQgNDQuMzIwMyAxMTUuMTI1IDQ0LjYzMjhDMTE1LjI3NiA0NC45NDUzIDExNS40OSA0NS4xOTUzIDExNS43NjYgNDUuMzgyOEMxMTYuMDQ3IDQ1LjU2NTEgMTE2LjQwNiA0NS42NTYyIDExNi44NDQgNDUuNjU2MkMxMTcuMTgyIDQ1LjY1NjIgMTE3LjQ3MSA0NS41ODMzIDExNy43MTEgNDUuNDM3NUMxMTcuOTUxIDQ1LjI5MTcgMTE4LjE0NiA0NS4wOTExIDExOC4yOTcgNDQuODM1OUMxMTguNDUzIDQ0LjU3NTUgMTE4LjU2OCA0NC4yNzYgMTE4LjY0MSA0My45Mzc1QzExOC43MTQgNDMuNTk5IDExOC43NSA0My4yMzk2IDExOC43NSA0Mi44NTk0Wk0xMjYuMjExIDQ3LjE1NjJDMTI1LjU4NiA0Ny4xNTYyIDEyNS4wMjEgNDcuMDU0NyAxMjQuNTE2IDQ2Ljg1MTZDMTI0LjAxNiA0Ni42NDMyIDEyMy41ODkgNDYuMzU0MiAxMjMuMjM0IDQ1Ljk4NDRDMTIyLjg4NSA0NS42MTQ2IDEyMi42MTcgNDUuMTc5NyAxMjIuNDMgNDQuNjc5N0MxMjIuMjQyIDQ0LjE3OTcgMTIyLjE0OCA0My42NDA2IDEyMi4xNDggNDMuMDYyNVY0Mi43NUMxMjIuMTQ4IDQyLjA4ODUgMTIyLjI0NSA0MS40ODk2IDEyMi40MzggNDAuOTUzMUMxMjIuNjMgNDAuNDE2NyAxMjIuODk4IDM5Ljk1ODMgMTIzLjI0MiAzOS41NzgxQzEyMy41ODYgMzkuMTkyNyAxMjMuOTkyIDM4Ljg5ODQgMTI0LjQ2MSAzOC42OTUzQzEyNC45MyAzOC40OTIyIDEyNS40MzggMzguMzkwNiAxMjUuOTg0IDM4LjM5MDZDMTI2LjU4OSAzOC4zOTA2IDEyNy4xMTcgMzguNDkyMiAxMjcuNTcgMzguNjk1M0MxMjguMDIzIDM4Ljg5ODQgMTI4LjM5OCAzOS4xODQ5IDEyOC42OTUgMzkuNTU0N0MxMjguOTk3IDM5LjkxOTMgMTI5LjIyMSA0MC4zNTQyIDEyOS4zNjcgNDAuODU5NEMxMjkuNTE4IDQxLjM2NDYgMTI5LjU5NCA0MS45MjE5IDEyOS41OTQgNDIuNTMxMlY0My4zMzU5SDEyMy4wNjJWNDEuOTg0NEgxMjcuNzM0VjQxLjgzNTlDMTI3LjcyNCA0MS40OTc0IDEyNy42NTYgNDEuMTc5NyAxMjcuNTMxIDQwLjg4MjhDMTI3LjQxMSA0MC41ODU5IDEyNy4yMjcgNDAuMzQ2NCAxMjYuOTc3IDQwLjE2NDFDMTI2LjcyNyAzOS45ODE4IDEyNi4zOTMgMzkuODkwNiAxMjUuOTc3IDM5Ljg5MDZDMTI1LjY2NCAzOS44OTA2IDEyNS4zODUgMzkuOTU4MyAxMjUuMTQxIDQwLjA5MzhDMTI0LjkwMSA0MC4yMjQgMTI0LjcwMSA0MC40MTQxIDEyNC41MzkgNDAuNjY0MUMxMjQuMzc4IDQwLjkxNDEgMTI0LjI1MyA0MS4yMTYxIDEyNC4xNjQgNDEuNTcwM0MxMjQuMDgxIDQxLjkxOTMgMTI0LjAzOSA0Mi4zMTI1IDEyNC4wMzkgNDIuNzVWNDMuMDYyNUMxMjQuMDM5IDQzLjQzMjMgMTI0LjA4OSA0My43NzYgMTI0LjE4OCA0NC4wOTM4QzEyNC4yOTIgNDQuNDA2MiAxMjQuNDQzIDQ0LjY3OTcgMTI0LjY0MSA0NC45MTQxQzEyNC44MzkgNDUuMTQ4NCAxMjUuMDc4IDQ1LjMzMzMgMTI1LjM1OSA0NS40Njg4QzEyNS42NDEgNDUuNTk5IDEyNS45NjEgNDUuNjY0MSAxMjYuMzIgNDUuNjY0MUMxMjYuNzczIDQ1LjY2NDEgMTI3LjE3NyA0NS41NzI5IDEyNy41MzEgNDUuMzkwNkMxMjcuODg1IDQ1LjIwODMgMTI4LjE5MyA0NC45NTA1IDEyOC40NTMgNDQuNjE3MkwxMjkuNDQ1IDQ1LjU3ODFDMTI5LjI2MyA0NS44NDM4IDEyOS4wMjYgNDYuMDk5IDEyOC43MzQgNDYuMzQzOEMxMjguNDQzIDQ2LjU4MzMgMTI4LjA4NiA0Ni43Nzg2IDEyNy42NjQgNDYuOTI5N0MxMjcuMjQ3IDQ3LjA4MDcgMTI2Ljc2MyA0Ny4xNTYyIDEyNi4yMTEgNDcuMTU2MlpNMTMzLjIwMyA0MC4xNTYyVjQ3SDEzMS4zMlYzOC41NDY5SDEzMy4xMTdMMTMzLjIwMyA0MC4xNTYyWk0xMzUuNzg5IDM4LjQ5MjJMMTM1Ljc3MyA0MC4yNDIyQzEzNS42NTkgNDAuMjIxNCAxMzUuNTM0IDQwLjIwNTcgMTM1LjM5OCA0MC4xOTUzQzEzNS4yNjggNDAuMTg0OSAxMzUuMTM4IDQwLjE3OTcgMTM1LjAwOCA0MC4xNzk3QzEzNC42ODUgNDAuMTc5NyAxMzQuNDAxIDQwLjIyNjYgMTM0LjE1NiA0MC4zMjAzQzEzMy45MTEgNDAuNDA4OSAxMzMuNzA2IDQwLjUzOTEgMTMzLjUzOSA0MC43MTA5QzEzMy4zNzggNDAuODc3NiAxMzMuMjUzIDQxLjA4MDcgMTMzLjE2NCA0MS4zMjAzQzEzMy4wNzYgNDEuNTU5OSAxMzMuMDIzIDQxLjgyODEgMTMzLjAwOCA0Mi4xMjVMMTMyLjU3OCA0Mi4xNTYyQzEzMi41NzggNDEuNjI1IDEzMi42MyA0MS4xMzI4IDEzMi43MzQgNDAuNjc5N0MxMzIuODM5IDQwLjIyNjYgMTMyLjk5NSAzOS44MjgxIDEzMy4yMDMgMzkuNDg0NEMxMzMuNDE3IDM5LjE0MDYgMTMzLjY4MiAzOC44NzI0IDEzNCAzOC42Nzk3QzEzNC4zMjMgMzguNDg3IDEzNC42OTUgMzguMzkwNiAxMzUuMTE3IDM4LjM5MDZDMTM1LjIzMiAzOC4zOTA2IDEzNS4zNTQgMzguNDAxIDEzNS40ODQgMzguNDIxOUMxMzUuNjIgMzguNDQyNyAxMzUuNzIxIDM4LjQ2NjEgMTM1Ljc4OSAzOC40OTIyWk0xNDEuNzAzIDQ1LjMwNDdWNDEuMjczNEMxNDEuNzAzIDQwLjk3MTQgMTQxLjY0OCA0MC43MTA5IDE0MS41MzkgNDAuNDkyMkMxNDEuNDMgNDAuMjczNCAxNDEuMjYzIDQwLjEwNDIgMTQxLjAzOSAzOS45ODQ0QzE0MC44MiAzOS44NjQ2IDE0MC41NDQgMzkuODA0NyAxNDAuMjExIDM5LjgwNDdDMTM5LjkwNCAzOS44MDQ3IDEzOS42MzggMzkuODU2OCAxMzkuNDE0IDM5Ljk2MDlDMTM5LjE5IDQwLjA2NTEgMTM5LjAxNiA0MC4yMDU3IDEzOC44OTEgNDAuMzgyOEMxMzguNzY2IDQwLjU1OTkgMTM4LjcwMyA0MC43NjA0IDEzOC43MDMgNDAuOTg0NEgxMzYuODI4QzEzNi44MjggNDAuNjUxIDEzNi45MDkgNDAuMzI4MSAxMzcuMDcgNDAuMDE1NkMxMzcuMjMyIDM5LjcwMzEgMTM3LjQ2NiAzOS40MjQ1IDEzNy43NzMgMzkuMTc5N0MxMzguMDgxIDM4LjkzNDkgMTM4LjQ0OCAzOC43NDIyIDEzOC44NzUgMzguNjAxNkMxMzkuMzAyIDM4LjQ2MDkgMTM5Ljc4MSAzOC4zOTA2IDE0MC4zMTIgMzguMzkwNkMxNDAuOTQ4IDM4LjM5MDYgMTQxLjUxIDM4LjQ5NzQgMTQyIDM4LjcxMDlDMTQyLjQ5NSAzOC45MjQ1IDE0Mi44ODMgMzkuMjQ3NCAxNDMuMTY0IDM5LjY3OTdDMTQzLjQ1MSA0MC4xMDY4IDE0My41OTQgNDAuNjQzMiAxNDMuNTk0IDQxLjI4OTFWNDUuMDQ2OUMxNDMuNTk0IDQ1LjQzMjMgMTQzLjYyIDQ1Ljc3ODYgMTQzLjY3MiA0Ni4wODU5QzE0My43MjkgNDYuMzg4IDE0My44MSA0Ni42NTEgMTQzLjkxNCA0Ni44NzVWNDdIMTQxLjk4NEMxNDEuODk2IDQ2Ljc5NjkgMTQxLjgyNiA0Ni41MzkxIDE0MS43NzMgNDYuMjI2NkMxNDEuNzI3IDQ1LjkwODkgMTQxLjcwMyA0NS42MDE2IDE0MS43MDMgNDUuMzA0N1pNMTQxLjk3NyA0MS44NTk0TDE0MS45OTIgNDMuMDIzNEgxNDAuNjQxQzE0MC4yOTIgNDMuMDIzNCAxMzkuOTg0IDQzLjA1NzMgMTM5LjcxOSA0My4xMjVDMTM5LjQ1MyA0My4xODc1IDEzOS4yMzIgNDMuMjgxMiAxMzkuMDU1IDQzLjQwNjJDMTM4Ljg3OCA0My41MzEyIDEzOC43NDUgNDMuNjgyMyAxMzguNjU2IDQzLjg1OTRDMTM4LjU2OCA0NC4wMzY1IDEzOC41MjMgNDQuMjM3IDEzOC41MjMgNDQuNDYwOUMxMzguNTIzIDQ0LjY4NDkgMTM4LjU3NiA0NC44OTA2IDEzOC42OCA0NS4wNzgxQzEzOC43ODQgNDUuMjYwNCAxMzguOTM1IDQ1LjQwMzYgMTM5LjEzMyA0NS41MDc4QzEzOS4zMzYgNDUuNjEyIDEzOS41ODEgNDUuNjY0MSAxMzkuODY3IDQ1LjY2NDFDMTQwLjI1MyA0NS42NjQxIDE0MC41ODkgNDUuNTg1OSAxNDAuODc1IDQ1LjQyOTdDMTQxLjE2NyA0NS4yNjgyIDE0MS4zOTYgNDUuMDcyOSAxNDEuNTYyIDQ0Ljg0MzhDMTQxLjcyOSA0NC42MDk0IDE0MS44MTggNDQuMzg4IDE0MS44MjggNDQuMTc5N0wxNDIuNDM4IDQ1LjAxNTZDMTQyLjM3NSA0NS4yMjkyIDE0Mi4yNjggNDUuNDU4MyAxNDIuMTE3IDQ1LjcwMzFDMTQxLjk2NiA0NS45NDc5IDE0MS43NjggNDYuMTgyMyAxNDEuNTIzIDQ2LjQwNjJDMTQxLjI4NCA0Ni42MjUgMTQwLjk5NSA0Ni44MDQ3IDE0MC42NTYgNDYuOTQ1M0MxNDAuMzIzIDQ3LjA4NTkgMTM5LjkzOCA0Ny4xNTYyIDEzOS41IDQ3LjE1NjJDMTM4Ljk0OCA0Ny4xNTYyIDEzOC40NTYgNDcuMDQ2OSAxMzguMDIzIDQ2LjgyODFDMTM3LjU5MSA0Ni42MDQyIDEzNy4yNTMgNDYuMzA0NyAxMzcuMDA4IDQ1LjkyOTdDMTM2Ljc2MyA0NS41NDk1IDEzNi42NDEgNDUuMTE5OCAxMzYuNjQxIDQ0LjY0MDZDMTM2LjY0MSA0NC4xOTI3IDEzNi43MjQgNDMuNzk2OSAxMzYuODkxIDQzLjQ1MzFDMTM3LjA2MiA0My4xMDQyIDEzNy4zMTIgNDIuODEyNSAxMzcuNjQxIDQyLjU3ODFDMTM3Ljk3NCA0Mi4zNDM4IDEzOC4zOCA0Mi4xNjY3IDEzOC44NTkgNDIuMDQ2OUMxMzkuMzM5IDQxLjkyMTkgMTM5Ljg4NSA0MS44NTk0IDE0MC41IDQxLjg1OTRIMTQxLjk3N1pNMTQ5LjY4OCAzOC41NDY5VjM5LjkyMTlIMTQ0LjkyMlYzOC41NDY5SDE0OS42ODhaTTE0Ni4yOTcgMzYuNDc2NkgxNDguMThWNDQuNjY0MUMxNDguMTggNDQuOTI0NSAxNDguMjE2IDQ1LjEyNSAxNDguMjg5IDQ1LjI2NTZDMTQ4LjM2NyA0NS40MDEgMTQ4LjQ3NCA0NS40OTIyIDE0OC42MDkgNDUuNTM5MUMxNDguNzQ1IDQ1LjU4NTkgMTQ4LjkwNCA0NS42MDk0IDE0OS4wODYgNDUuNjA5NEMxNDkuMjE2IDQ1LjYwOTQgMTQ5LjM0MSA0NS42MDE2IDE0OS40NjEgNDUuNTg1OUMxNDkuNTgxIDQ1LjU3MDMgMTQ5LjY3NyA0NS41NTQ3IDE0OS43NSA0NS41MzkxTDE0OS43NTggNDYuOTc2NkMxNDkuNjAyIDQ3LjAyMzQgMTQ5LjQxOSA0Ny4wNjUxIDE0OS4yMTEgNDcuMTAxNkMxNDkuMDA4IDQ3LjEzOCAxNDguNzczIDQ3LjE1NjIgMTQ4LjUwOCA0Ny4xNTYyQzE0OC4wNzYgNDcuMTU2MiAxNDcuNjkzIDQ3LjA4MDcgMTQ3LjM1OSA0Ni45Mjk3QzE0Ny4wMjYgNDYuNzczNCAxNDYuNzY2IDQ2LjUyMDggMTQ2LjU3OCA0Ni4xNzE5QzE0Ni4zOTEgNDUuODIyOSAxNDYuMjk3IDQ1LjM1OTQgMTQ2LjI5NyA0NC43ODEyVjM2LjQ3NjZaTTE1Ni40NzcgNDUuMDA3OFYzOC41NDY5SDE1OC4zNjdWNDdIMTU2LjU4NkwxNTYuNDc3IDQ1LjAwNzhaTTE1Ni43NDIgNDMuMjVMMTU3LjM3NSA0My4yMzQ0QzE1Ny4zNzUgNDMuODAyMSAxNTcuMzEyIDQ0LjMyNTUgMTU3LjE4OCA0NC44MDQ3QzE1Ny4wNjIgNDUuMjc4NiAxNTYuODcgNDUuNjkyNyAxNTYuNjA5IDQ2LjA0NjlDMTU2LjM0OSA0Ni4zOTU4IDE1Ni4wMTYgNDYuNjY5MyAxNTUuNjA5IDQ2Ljg2NzJDMTU1LjIwMyA0Ny4wNTk5IDE1NC43MTYgNDcuMTU2MiAxNTQuMTQ4IDQ3LjE1NjJDMTUzLjczNyA0Ny4xNTYyIDE1My4zNTkgNDcuMDk2NCAxNTMuMDE2IDQ2Ljk3NjZDMTUyLjY3MiA0Ni44NTY4IDE1Mi4zNzUgNDYuNjcxOSAxNTIuMTI1IDQ2LjQyMTlDMTUxLjg4IDQ2LjE3MTkgMTUxLjY5IDQ1Ljg0NjQgMTUxLjU1NSA0NS40NDUzQzE1MS40MTkgNDUuMDQ0MyAxNTEuMzUyIDQ0LjU2NTEgMTUxLjM1MiA0NC4wMDc4VjM4LjU0NjlIMTUzLjIzNFY0NC4wMjM0QzE1My4yMzQgNDQuMzMwNyAxNTMuMjcxIDQ0LjU4ODUgMTUzLjM0NCA0NC43OTY5QzE1My40MTcgNDUgMTUzLjUxNiA0NS4xNjQxIDE1My42NDEgNDUuMjg5MUMxNTMuNzY2IDQ1LjQxNDEgMTUzLjkxMSA0NS41MDI2IDE1NC4wNzggNDUuNTU0N0MxNTQuMjQ1IDQ1LjYwNjggMTU0LjQyMiA0NS42MzI4IDE1NC42MDkgNDUuNjMyOEMxNTUuMTQ2IDQ1LjYzMjggMTU1LjU2OCA0NS41Mjg2IDE1NS44NzUgNDUuMzIwM0MxNTYuMTg4IDQ1LjEwNjggMTU2LjQwOSA0NC44MjAzIDE1Ni41MzkgNDQuNDYwOUMxNTYuNjc0IDQ0LjEwMTYgMTU2Ljc0MiA0My42OTc5IDE1Ni43NDIgNDMuMjVaTTE2Mi40MzggNDAuMTU2MlY0N0gxNjAuNTU1VjM4LjU0NjlIMTYyLjM1MkwxNjIuNDM4IDQwLjE1NjJaTTE2NS4wMjMgMzguNDkyMkwxNjUuMDA4IDQwLjI0MjJDMTY0Ljg5MyA0MC4yMjE0IDE2NC43NjggNDAuMjA1NyAxNjQuNjMzIDQwLjE5NTNDMTY0LjUwMyA0MC4xODQ5IDE2NC4zNzIgNDAuMTc5NyAxNjQuMjQyIDQwLjE3OTdDMTYzLjkxOSA0MC4xNzk3IDE2My42MzUgNDAuMjI2NiAxNjMuMzkxIDQwLjMyMDNDMTYzLjE0NiA0MC40MDg5IDE2Mi45NCA0MC41MzkxIDE2Mi43NzMgNDAuNzEwOUMxNjIuNjEyIDQwLjg3NzYgMTYyLjQ4NyA0MS4wODA3IDE2Mi4zOTggNDEuMzIwM0MxNjIuMzEgNDEuNTU5OSAxNjIuMjU4IDQxLjgyODEgMTYyLjI0MiA0Mi4xMjVMMTYxLjgxMiA0Mi4xNTYyQzE2MS44MTIgNDEuNjI1IDE2MS44NjUgNDEuMTMyOCAxNjEuOTY5IDQwLjY3OTdDMTYyLjA3MyA0MC4yMjY2IDE2Mi4yMjkgMzkuODI4MSAxNjIuNDM4IDM5LjQ4NDRDMTYyLjY1MSAzOS4xNDA2IDE2Mi45MTcgMzguODcyNCAxNjMuMjM0IDM4LjY3OTdDMTYzLjU1NyAzOC40ODcgMTYzLjkzIDM4LjM5MDYgMTY0LjM1MiAzOC4zOTA2QzE2NC40NjYgMzguMzkwNiAxNjQuNTg5IDM4LjQwMSAxNjQuNzE5IDM4LjQyMTlDMTY0Ljg1NCAzOC40NDI3IDE2NC45NTYgMzguNDY2MSAxNjUuMDIzIDM4LjQ5MjJaTTE3MC4wMjMgNDcuMTU2MkMxNjkuMzk4IDQ3LjE1NjIgMTY4LjgzMyA0Ny4wNTQ3IDE2OC4zMjggNDYuODUxNkMxNjcuODI4IDQ2LjY0MzIgMTY3LjQwMSA0Ni4zNTQyIDE2Ny4wNDcgNDUuOTg0NEMxNjYuNjk4IDQ1LjYxNDYgMTY2LjQzIDQ1LjE3OTcgMTY2LjI0MiA0NC42Nzk3QzE2Ni4wNTUgNDQuMTc5NyAxNjUuOTYxIDQzLjY0MDYgMTY1Ljk2MSA0My4wNjI1VjQyLjc1QzE2NS45NjEgNDIuMDg4NSAxNjYuMDU3IDQxLjQ4OTYgMTY2LjI1IDQwLjk1MzFDMTY2LjQ0MyA0MC40MTY3IDE2Ni43MTEgMzkuOTU4MyAxNjcuMDU1IDM5LjU3ODFDMTY3LjM5OCAzOS4xOTI3IDE2Ny44MDUgMzguODk4NCAxNjguMjczIDM4LjY5NTNDMTY4Ljc0MiAzOC40OTIyIDE2OS4yNSAzOC4zOTA2IDE2OS43OTcgMzguMzkwNkMxNzAuNDAxIDM4LjM5MDYgMTcwLjkzIDM4LjQ5MjIgMTcxLjM4MyAzOC42OTUzQzE3MS44MzYgMzguODk4NCAxNzIuMjExIDM5LjE4NDkgMTcyLjUwOCAzOS41NTQ3QzE3Mi44MSAzOS45MTkzIDE3My4wMzQgNDAuMzU0MiAxNzMuMTggNDAuODU5NEMxNzMuMzMxIDQxLjM2NDYgMTczLjQwNiA0MS45MjE5IDE3My40MDYgNDIuNTMxMlY0My4zMzU5SDE2Ni44NzVWNDEuOTg0NEgxNzEuNTQ3VjQxLjgzNTlDMTcxLjUzNiA0MS40OTc0IDE3MS40NjkgNDEuMTc5NyAxNzEuMzQ0IDQwLjg4MjhDMTcxLjIyNCA0MC41ODU5IDE3MS4wMzkgNDAuMzQ2NCAxNzAuNzg5IDQwLjE2NDFDMTcwLjUzOSAzOS45ODE4IDE3MC4yMDYgMzkuODkwNiAxNjkuNzg5IDM5Ljg5MDZDMTY5LjQ3NyAzOS44OTA2IDE2OS4xOTggMzkuOTU4MyAxNjguOTUzIDQwLjA5MzhDMTY4LjcxNCA0MC4yMjQgMTY4LjUxMyA0MC40MTQxIDE2OC4zNTIgNDAuNjY0MUMxNjguMTkgNDAuOTE0MSAxNjguMDY1IDQxLjIxNjEgMTY3Ljk3NyA0MS41NzAzQzE2Ny44OTMgNDEuOTE5MyAxNjcuODUyIDQyLjMxMjUgMTY3Ljg1MiA0Mi43NVY0My4wNjI1QzE2Ny44NTIgNDMuNDMyMyAxNjcuOTAxIDQzLjc3NiAxNjggNDQuMDkzOEMxNjguMTA0IDQ0LjQwNjIgMTY4LjI1NSA0NC42Nzk3IDE2OC40NTMgNDQuOTE0MUMxNjguNjUxIDQ1LjE0ODQgMTY4Ljg5MSA0NS4zMzMzIDE2OS4xNzIgNDUuNDY4OEMxNjkuNDUzIDQ1LjU5OSAxNjkuNzczIDQ1LjY2NDEgMTcwLjEzMyA0NS42NjQxQzE3MC41ODYgNDUuNjY0MSAxNzAuOTkgNDUuNTcyOSAxNzEuMzQ0IDQ1LjM5MDZDMTcxLjY5OCA0NS4yMDgzIDE3Mi4wMDUgNDQuOTUwNSAxNzIuMjY2IDQ0LjYxNzJMMTczLjI1OCA0NS41NzgxQzE3My4wNzYgNDUuODQzOCAxNzIuODM5IDQ2LjA5OSAxNzIuNTQ3IDQ2LjM0MzhDMTcyLjI1NSA0Ni41ODMzIDE3MS44OTggNDYuNzc4NiAxNzEuNDc3IDQ2LjkyOTdDMTcxLjA2IDQ3LjA4MDcgMTcwLjU3NiA0Ny4xNTYyIDE3MC4wMjMgNDcuMTU2MloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuODciLz4KPHBhdGggZD0iTTg2LjIxMDkgNjQuODM0VjY2SDgxLjkyNzdWNjQuODM0SDg2LjIxMDlaTTgyLjMzNzkgNTcuNDY4OFY2Nkg4MC44NjcyVjU3LjQ2ODhIODIuMzM3OVpNOTEuMDMxMiA2NC43Mjg1VjYxLjcwNTFDOTEuMDMxMiA2MS40Nzg1IDkwLjk5MDIgNjEuMjgzMiA5MC45MDgyIDYxLjExOTFDOTAuODI2MiA2MC45NTUxIDkwLjcwMTIgNjAuODI4MSA5MC41MzMyIDYwLjczODNDOTAuMzY5MSA2MC42NDg0IDkwLjE2MjEgNjAuNjAzNSA4OS45MTIxIDYwLjYwMzVDODkuNjgxNiA2MC42MDM1IDg5LjQ4MjQgNjAuNjQyNiA4OS4zMTQ1IDYwLjcyMDdDODkuMTQ2NSA2MC43OTg4IDg5LjAxNTYgNjAuOTA0MyA4OC45MjE5IDYxLjAzNzFDODguODI4MSA2MS4xNjk5IDg4Ljc4MTIgNjEuMzIwMyA4OC43ODEyIDYxLjQ4ODNIODcuMzc1Qzg3LjM3NSA2MS4yMzgzIDg3LjQzNTUgNjAuOTk2MSA4Ny41NTY2IDYwLjc2MTdDODcuNjc3NyA2MC41MjczIDg3Ljg1MzUgNjAuMzE4NCA4OC4wODQgNjAuMTM0OEM4OC4zMTQ1IDU5Ljk1MTIgODguNTg5OCA1OS44MDY2IDg4LjkxMDIgNTkuNzAxMkM4OS4yMzA1IDU5LjU5NTcgODkuNTg5OCA1OS41NDMgODkuOTg4MyA1OS41NDNDOTAuNDY0OCA1OS41NDMgOTAuODg2NyA1OS42MjMgOTEuMjUzOSA1OS43ODMyQzkxLjYyNSA1OS45NDM0IDkxLjkxNiA2MC4xODU1IDkyLjEyNyA2MC41MDk4QzkyLjM0MTggNjAuODMwMSA5Mi40NDkyIDYxLjIzMjQgOTIuNDQ5MiA2MS43MTY4VjY0LjUzNTJDOTIuNDQ5MiA2NC44MjQyIDkyLjQ2ODggNjUuMDg0IDkyLjUwNzggNjUuMzE0NUM5Mi41NTA4IDY1LjU0MSA5Mi42MTEzIDY1LjczODMgOTIuNjg5NSA2NS45MDYyVjY2SDkxLjI0MjJDOTEuMTc1OCA2NS44NDc3IDkxLjEyMyA2NS42NTQzIDkxLjA4NCA2NS40MTk5QzkxLjA0ODggNjUuMTgxNiA5MS4wMzEyIDY0Ljk1MTIgOTEuMDMxMiA2NC43Mjg1Wk05MS4yMzYzIDYyLjE0NDVMOTEuMjQ4IDYzLjAxNzZIOTAuMjM0NEM4OS45NzI3IDYzLjAxNzYgODkuNzQyMiA2My4wNDMgODkuNTQzIDYzLjA5MzhDODkuMzQzOCA2My4xNDA2IDg5LjE3NzcgNjMuMjEwOSA4OS4wNDQ5IDYzLjMwNDdDODguOTEyMSA2My4zOTg0IDg4LjgxMjUgNjMuNTExNyA4OC43NDYxIDYzLjY0NDVDODguNjc5NyA2My43NzczIDg4LjY0NjUgNjMuOTI3NyA4OC42NDY1IDY0LjA5NTdDODguNjQ2NSA2NC4yNjM3IDg4LjY4NTUgNjQuNDE4IDg4Ljc2MzcgNjQuNTU4NkM4OC44NDE4IDY0LjY5NTMgODguOTU1MSA2NC44MDI3IDg5LjEwMzUgNjQuODgwOUM4OS4yNTU5IDY0Ljk1OSA4OS40Mzk1IDY0Ljk5OCA4OS42NTQzIDY0Ljk5OEM4OS45NDM0IDY0Ljk5OCA5MC4xOTUzIDY0LjkzOTUgOTAuNDEwMiA2NC44MjIzQzkwLjYyODkgNjQuNzAxMiA5MC44MDA4IDY0LjU1NDcgOTAuOTI1OCA2NC4zODI4QzkxLjA1MDggNjQuMjA3IDkxLjExNzIgNjQuMDQxIDkxLjEyNSA2My44ODQ4TDkxLjU4MiA2NC41MTE3QzkxLjUzNTIgNjQuNjcxOSA5MS40NTUxIDY0Ljg0MzggOTEuMzQxOCA2NS4wMjczQzkxLjIyODUgNjUuMjEwOSA5MS4wODAxIDY1LjM4NjcgOTAuODk2NSA2NS41NTQ3QzkwLjcxNjggNjUuNzE4OCA5MC41IDY1Ljg1MzUgOTAuMjQ2MSA2NS45NTlDODkuOTk2MSA2Ni4wNjQ1IDg5LjcwNyA2Ni4xMTcyIDg5LjM3ODkgNjYuMTE3MkM4OC45NjQ4IDY2LjExNzIgODguNTk1NyA2Ni4wMzUyIDg4LjI3MTUgNjUuODcxMUM4Ny45NDczIDY1LjcwMzEgODcuNjkzNCA2NS40Nzg1IDg3LjUwOTggNjUuMTk3M0M4Ny4zMjYyIDY0LjkxMjEgODcuMjM0NCA2NC41ODk4IDg3LjIzNDQgNjQuMjMwNUM4Ny4yMzQ0IDYzLjg5NDUgODcuMjk2OSA2My41OTc3IDg3LjQyMTkgNjMuMzM5OEM4Ny41NTA4IDYzLjA3ODEgODcuNzM4MyA2Mi44NTk0IDg3Ljk4NDQgNjIuNjgzNkM4OC4yMzQ0IDYyLjUwNzggODguNTM5MSA2Mi4zNzUgODguODk4NCA2Mi4yODUyQzg5LjI1NzggNjIuMTkxNCA4OS42NjggNjIuMTQ0NSA5MC4xMjg5IDYyLjE0NDVIOTEuMjM2M1pNOTcuNzMyNCA2NC4yODMyQzk3LjczMjQgNjQuMTQyNiA5Ny42OTczIDY0LjAxNTYgOTcuNjI3IDYzLjkwMjNDOTcuNTU2NiA2My43ODUyIDk3LjQyMTkgNjMuNjc5NyA5Ny4yMjI3IDYzLjU4NTlDOTcuMDI3MyA2My40OTIyIDk2LjczODMgNjMuNDA2MiA5Ni4zNTU1IDYzLjMyODFDOTYuMDE5NSA2My4yNTM5IDk1LjcxMDkgNjMuMTY2IDk1LjQyOTcgNjMuMDY0NUM5NS4xNTIzIDYyLjk1OSA5NC45MTQxIDYyLjgzMiA5NC43MTQ4IDYyLjY4MzZDOTQuNTE1NiA2Mi41MzUyIDk0LjM2MTMgNjIuMzU5NCA5NC4yNTIgNjIuMTU2MkM5NC4xNDI2IDYxLjk1MzEgOTQuMDg3OSA2MS43MTg4IDk0LjA4NzkgNjEuNDUzMUM5NC4wODc5IDYxLjE5NTMgOTQuMTQ0NSA2MC45NTEyIDk0LjI1NzggNjAuNzIwN0M5NC4zNzExIDYwLjQ5MDIgOTQuNTMzMiA2MC4yODcxIDk0Ljc0NDEgNjAuMTExM0M5NC45NTUxIDU5LjkzNTUgOTUuMjEwOSA1OS43OTY5IDk1LjUxMTcgNTkuNjk1M0M5NS44MTY0IDU5LjU5MzggOTYuMTU2MiA1OS41NDMgOTYuNTMxMiA1OS41NDNDOTcuMDYyNSA1OS41NDMgOTcuNTE3NiA1OS42MzI4IDk3Ljg5NjUgNTkuODEyNUM5OC4yNzkzIDU5Ljk4ODMgOTguNTcyMyA2MC4yMjg1IDk4Ljc3NTQgNjAuNTMzMkM5OC45Nzg1IDYwLjgzNCA5OS4wODAxIDYxLjE3MzggOTkuMDgwMSA2MS41NTI3SDk3LjY2OEM5Ny42NjggNjEuMzg0OCA5Ny42MjUgNjEuMjI4NSA5Ny41MzkxIDYxLjA4NEM5Ny40NTcgNjAuOTM1NSA5Ny4zMzIgNjAuODE2NCA5Ny4xNjQxIDYwLjcyNjZDOTYuOTk2MSA2MC42MzI4IDk2Ljc4NTIgNjAuNTg1OSA5Ni41MzEyIDYwLjU4NTlDOTYuMjg5MSA2MC41ODU5IDk2LjA4NzkgNjAuNjI1IDk1LjkyNzcgNjAuNzAzMUM5NS43NzE1IDYwLjc3NzMgOTUuNjU0MyA2MC44NzUgOTUuNTc2MiA2MC45OTYxQzk1LjUwMiA2MS4xMTcyIDk1LjQ2NDggNjEuMjUgOTUuNDY0OCA2MS4zOTQ1Qzk1LjQ2NDggNjEuNSA5NS40ODQ0IDYxLjU5NTcgOTUuNTIzNCA2MS42ODE2Qzk1LjU2NjQgNjEuNzYzNyA5NS42MzY3IDYxLjgzOTggOTUuNzM0NCA2MS45MTAyQzk1LjgzMiA2MS45NzY2IDk1Ljk2NDggNjIuMDM5MSA5Ni4xMzI4IDYyLjA5NzdDOTYuMzA0NyA2Mi4xNTYyIDk2LjUxOTUgNjIuMjEyOSA5Ni43NzczIDYyLjI2NzZDOTcuMjYxNyA2Mi4zNjkxIDk3LjY3NzcgNjIuNSA5OC4wMjU0IDYyLjY2MDJDOTguMzc3IDYyLjgxNjQgOTguNjQ2NSA2My4wMTk1IDk4LjgzNCA2My4yNjk1Qzk5LjAyMTUgNjMuNTE1NiA5OS4xMTUyIDYzLjgyODEgOTkuMTE1MiA2NC4yMDdDOTkuMTE1MiA2NC40ODgzIDk5LjA1NDcgNjQuNzQ2MSA5OC45MzM2IDY0Ljk4MDVDOTguODE2NCA2NS4yMTA5IDk4LjY0NDUgNjUuNDEyMSA5OC40MTggNjUuNTg0Qzk4LjE5MTQgNjUuNzUyIDk3LjkxOTkgNjUuODgyOCA5Ny42MDM1IDY1Ljk3NjZDOTcuMjkxIDY2LjA3MDMgOTYuOTM5NSA2Ni4xMTcyIDk2LjU0ODggNjYuMTE3MkM5NS45NzQ2IDY2LjExNzIgOTUuNDg4MyA2Ni4wMTU2IDk1LjA4OTggNjUuODEyNUM5NC42OTE0IDY1LjYwNTUgOTQuMzg4NyA2NS4zNDE4IDk0LjE4MTYgNjUuMDIxNUM5My45Nzg1IDY0LjY5NzMgOTMuODc3IDY0LjM2MTMgOTMuODc3IDY0LjAxMzdIOTUuMjQyMkM5NS4yNTc4IDY0LjI3NTQgOTUuMzMwMSA2NC40ODQ0IDk1LjQ1OSA2NC42NDA2Qzk1LjU5MTggNjQuNzkzIDk1Ljc1NTkgNjQuOTA0MyA5NS45NTEyIDY0Ljk3NDZDOTYuMTUwNCA2NS4wNDEgOTYuMzU1NSA2NS4wNzQyIDk2LjU2NjQgNjUuMDc0MkM5Ni44MjAzIDY1LjA3NDIgOTcuMDMzMiA2NS4wNDEgOTcuMjA1MSA2NC45NzQ2Qzk3LjM3NyA2NC45MDQzIDk3LjUwNzggNjQuODEwNSA5Ny41OTc3IDY0LjY5MzRDOTcuNjg3NSA2NC41NzIzIDk3LjczMjQgNjQuNDM1NSA5Ny43MzI0IDY0LjI4MzJaTTEwMy41MDggNTkuNjYwMlY2MC42OTE0SDk5LjkzMzZWNTkuNjYwMkgxMDMuNTA4Wk0xMDAuOTY1IDU4LjEwNzRIMTAyLjM3N1Y2NC4yNDhDMTAyLjM3NyA2NC40NDM0IDEwMi40MDQgNjQuNTkzOCAxMDIuNDU5IDY0LjY5OTJDMTAyLjUxOCA2NC44MDA4IDEwMi41OTggNjQuODY5MSAxMDIuNjk5IDY0LjkwNDNDMTAyLjgwMSA2NC45Mzk1IDEwMi45MiA2NC45NTcgMTAzLjA1NyA2NC45NTdDMTAzLjE1NCA2NC45NTcgMTAzLjI0OCA2NC45NTEyIDEwMy4zMzggNjQuOTM5NUMxMDMuNDI4IDY0LjkyNzcgMTAzLjUgNjQuOTE2IDEwMy41NTUgNjQuOTA0M0wxMDMuNTYxIDY1Ljk4MjRDMTAzLjQ0MyA2Ni4wMTc2IDEwMy4zMDcgNjYuMDQ4OCAxMDMuMTUgNjYuMDc2MkMxMDIuOTk4IDY2LjEwMzUgMTAyLjgyMiA2Ni4xMTcyIDEwMi42MjMgNjYuMTE3MkMxMDIuMjk5IDY2LjExNzIgMTAyLjAxMiA2Ni4wNjA1IDEwMS43NjIgNjUuOTQ3M0MxMDEuNTEyIDY1LjgzMDEgMTAxLjMxNiA2NS42NDA2IDEwMS4xNzYgNjUuMzc4OUMxMDEuMDM1IDY1LjExNzIgMTAwLjk2NSA2NC43Njk1IDEwMC45NjUgNjQuMzM1OVY1OC4xMDc0Wk0xMTEuOSA2NC41MDU5VjU5LjY2MDJIMTEzLjMxOFY2NkgxMTEuOTgyTDExMS45IDY0LjUwNTlaTTExMi4xIDYzLjE4NzVMMTEyLjU3NCA2My4xNzU4QzExMi41NzQgNjMuNjAxNiAxMTIuNTI3IDYzLjk5NDEgMTEyLjQzNCA2NC4zNTM1QzExMi4zNCA2NC43MDkgMTEyLjE5NSA2NS4wMTk1IDExMiA2NS4yODUyQzExMS44MDUgNjUuNTQ2OSAxMTEuNTU1IDY1Ljc1MiAxMTEuMjUgNjUuOTAwNEMxMTAuOTQ1IDY2LjA0NDkgMTEwLjU4IDY2LjExNzIgMTEwLjE1NCA2Ni4xMTcyQzEwOS44NDYgNjYuMTE3MiAxMDkuNTYyIDY2LjA3MjMgMTA5LjMwNSA2NS45ODI0QzEwOS4wNDcgNjUuODkyNiAxMDguODI0IDY1Ljc1MzkgMTA4LjYzNyA2NS41NjY0QzEwOC40NTMgNjUuMzc4OSAxMDguMzExIDY1LjEzNDggMTA4LjIwOSA2NC44MzRDMTA4LjEwNyA2NC41MzMyIDEwOC4wNTcgNjQuMTczOCAxMDguMDU3IDYzLjc1NTlWNTkuNjYwMkgxMDkuNDY5VjYzLjc2NzZDMTA5LjQ2OSA2My45OTggMTA5LjQ5NiA2NC4xOTE0IDEwOS41NTEgNjQuMzQ3N0MxMDkuNjA1IDY0LjUgMTA5LjY4IDY0LjYyMyAxMDkuNzczIDY0LjcxNjhDMTA5Ljg2NyA2NC44MTA1IDEwOS45NzcgNjQuODc3IDExMC4xMDIgNjQuOTE2QzExMC4yMjcgNjQuOTU1MSAxMTAuMzU5IDY0Ljk3NDYgMTEwLjUgNjQuOTc0NkMxMTAuOTAyIDY0Ljk3NDYgMTExLjIxOSA2NC44OTY1IDExMS40NDkgNjQuNzQwMkMxMTEuNjg0IDY0LjU4MDEgMTExLjg1IDY0LjM2NTIgMTExLjk0NyA2NC4wOTU3QzExMi4wNDkgNjMuODI2MiAxMTIuMSA2My41MjM0IDExMi4xIDYzLjE4NzVaTTExNi40MzQgNjAuODc4OVY2OC40Mzc1SDExNS4wMjFWNTkuNjYwMkgxMTYuMzIyTDExNi40MzQgNjAuODc4OVpNMTIwLjU2NCA2Mi43NzE1VjYyLjg5NDVDMTIwLjU2NCA2My4zNTU1IDEyMC41MSA2My43ODMyIDEyMC40IDY0LjE3NzdDMTIwLjI5NSA2NC41Njg0IDEyMC4xMzcgNjQuOTEwMiAxMTkuOTI2IDY1LjIwMzFDMTE5LjcxOSA2NS40OTIyIDExOS40NjMgNjUuNzE2OCAxMTkuMTU4IDY1Ljg3N0MxMTguODU0IDY2LjAzNzEgMTE4LjUwMiA2Ni4xMTcyIDExOC4xMDQgNjYuMTE3MkMxMTcuNzA5IDY2LjExNzIgMTE3LjM2MyA2Ni4wNDQ5IDExNy4wNjYgNjUuOTAwNEMxMTYuNzczIDY1Ljc1MiAxMTYuNTI1IDY1LjU0MyAxMTYuMzIyIDY1LjI3MzRDMTE2LjExOSA2NS4wMDM5IDExNS45NTUgNjQuNjg3NSAxMTUuODMgNjQuMzI0MkMxMTUuNzA5IDYzLjk1NyAxMTUuNjIzIDYzLjU1NDcgMTE1LjU3MiA2My4xMTcyVjYyLjY0MjZDMTE1LjYyMyA2Mi4xNzc3IDExNS43MDkgNjEuNzU1OSAxMTUuODMgNjEuMzc3QzExNS45NTUgNjAuOTk4IDExNi4xMTkgNjAuNjcxOSAxMTYuMzIyIDYwLjM5ODRDMTE2LjUyNSA2MC4xMjUgMTE2Ljc3MyA1OS45MTQxIDExNy4wNjYgNTkuNzY1NkMxMTcuMzU5IDU5LjYxNzIgMTE3LjcwMSA1OS41NDMgMTE4LjA5MiA1OS41NDNDMTE4LjQ5IDU5LjU0MyAxMTguODQ0IDU5LjYyMTEgMTE5LjE1MiA1OS43NzczQzExOS40NjEgNTkuOTI5NyAxMTkuNzIxIDYwLjE0ODQgMTE5LjkzMiA2MC40MzM2QzEyMC4xNDMgNjAuNzE0OCAxMjAuMzAxIDYxLjA1NDcgMTIwLjQwNiA2MS40NTMxQzEyMC41MTIgNjEuODQ3NyAxMjAuNTY0IDYyLjI4NzEgMTIwLjU2NCA2Mi43NzE1Wk0xMTkuMTUyIDYyLjg5NDVWNjIuNzcxNUMxMTkuMTUyIDYyLjQ3ODUgMTE5LjEyNSA2Mi4yMDcgMTE5LjA3IDYxLjk1N0MxMTkuMDE2IDYxLjcwMzEgMTE4LjkzIDYxLjQ4MDUgMTE4LjgxMiA2MS4yODkxQzExOC42OTUgNjEuMDk3NyAxMTguNTQ1IDYwLjk0OTIgMTE4LjM2MSA2MC44NDM4QzExOC4xODIgNjAuNzM0NCAxMTcuOTY1IDYwLjY3OTcgMTE3LjcxMSA2MC42Nzk3QzExNy40NjEgNjAuNjc5NyAxMTcuMjQ2IDYwLjcyMjcgMTE3LjA2NiA2MC44MDg2QzExNi44ODcgNjAuODkwNiAxMTYuNzM2IDYxLjAwNTkgMTE2LjYxNSA2MS4xNTQzQzExNi40OTQgNjEuMzAyNyAxMTYuNCA2MS40NzY2IDExNi4zMzQgNjEuNjc1OEMxMTYuMjY4IDYxLjg3MTEgMTE2LjIyMSA2Mi4wODQgMTE2LjE5MyA2Mi4zMTQ1VjYzLjQ1MTJDMTE2LjI0IDYzLjczMjQgMTE2LjMyIDYzLjk5MDIgMTE2LjQzNCA2NC4yMjQ2QzExNi41NDcgNjQuNDU5IDExNi43MDcgNjQuNjQ2NSAxMTYuOTE0IDY0Ljc4NzFDMTE3LjEyNSA2NC45MjM4IDExNy4zOTUgNjQuOTkyMiAxMTcuNzIzIDY0Ljk5MjJDMTE3Ljk3NyA2NC45OTIyIDExOC4xOTMgNjQuOTM3NSAxMTguMzczIDY0LjgyODFDMTE4LjU1MyA2NC43MTg4IDExOC42OTkgNjQuNTY4NCAxMTguODEyIDY0LjM3N0MxMTguOTMgNjQuMTgxNiAxMTkuMDE2IDYzLjk1NyAxMTkuMDcgNjMuNzAzMUMxMTkuMTI1IDYzLjQ0OTIgMTE5LjE1MiA2My4xNzk3IDExOS4xNTIgNjIuODk0NVpNMTI1Ljg4MyA2NC42ODc1VjU3SDEyNy4zMDFWNjZIMTI2LjAxOEwxMjUuODgzIDY0LjY4NzVaTTEyMS43NTggNjIuOTAwNFY2Mi43NzczQzEyMS43NTggNjIuMjk2OSAxMjEuODE0IDYxLjg1OTQgMTIxLjkyOCA2MS40NjQ4QzEyMi4wNDEgNjEuMDY2NCAxMjIuMjA1IDYwLjcyNDYgMTIyLjQyIDYwLjQzOTVDMTIyLjYzNSA2MC4xNTA0IDEyMi44OTYgNTkuOTI5NyAxMjMuMjA1IDU5Ljc3NzNDMTIzLjUxNCA1OS42MjExIDEyMy44NjEgNTkuNTQzIDEyNC4yNDggNTkuNTQzQzEyNC42MzEgNTkuNTQzIDEyNC45NjcgNTkuNjE3MiAxMjUuMjU2IDU5Ljc2NTZDMTI1LjU0NSA1OS45MTQxIDEyNS43OTEgNjAuMTI3IDEyNS45OTQgNjAuNDA0M0MxMjYuMTk3IDYwLjY3NzcgMTI2LjM1OSA2MS4wMDU5IDEyNi40OCA2MS4zODg3QzEyNi42MDIgNjEuNzY3NiAxMjYuNjg4IDYyLjE4OTUgMTI2LjczOCA2Mi42NTQzVjYzLjA0NjlDMTI2LjY4OCA2My41IDEyNi42MDIgNjMuOTE0MSAxMjYuNDggNjQuMjg5MUMxMjYuMzU5IDY0LjY2NDEgMTI2LjE5NyA2NC45ODgzIDEyNS45OTQgNjUuMjYxN0MxMjUuNzkxIDY1LjUzNTIgMTI1LjU0MyA2NS43NDYxIDEyNS4yNSA2NS44OTQ1QzEyNC45NjEgNjYuMDQzIDEyNC42MjMgNjYuMTE3MiAxMjQuMjM2IDY2LjExNzJDMTIzLjg1NCA2Ni4xMTcyIDEyMy41MDggNjYuMDM3MSAxMjMuMTk5IDY1Ljg3N0MxMjIuODk1IDY1LjcxNjggMTIyLjYzNSA2NS40OTIyIDEyMi40MiA2NS4yMDMxQzEyMi4yMDUgNjQuOTE0MSAxMjIuMDQxIDY0LjU3NDIgMTIxLjkyOCA2NC4xODM2QzEyMS44MTQgNjMuNzg5MSAxMjEuNzU4IDYzLjM2MTMgMTIxLjc1OCA2Mi45MDA0Wk0xMjMuMTcgNjIuNzc3M1Y2Mi45MDA0QzEyMy4xNyA2My4xODk1IDEyMy4xOTUgNjMuNDU5IDEyMy4yNDYgNjMuNzA5QzEyMy4zMDEgNjMuOTU5IDEyMy4zODUgNjQuMTc5NyAxMjMuNDk4IDY0LjM3MTFDMTIzLjYxMSA2NC41NTg2IDEyMy43NTggNjQuNzA3IDEyMy45MzggNjQuODE2NEMxMjQuMTIxIDY0LjkyMTkgMTI0LjM0IDY0Ljk3NDYgMTI0LjU5NCA2NC45NzQ2QzEyNC45MTQgNjQuOTc0NiAxMjUuMTc4IDY0LjkwNDMgMTI1LjM4NSA2NC43NjM3QzEyNS41OTIgNjQuNjIzIDEyNS43NTQgNjQuNDMzNiAxMjUuODcxIDY0LjE5NTNDMTI1Ljk5MiA2My45NTMxIDEyNi4wNzQgNjMuNjgzNiAxMjYuMTE3IDYzLjM4NjdWNjIuMzI2MkMxMjYuMDk0IDYyLjA5NTcgMTI2LjA0NSA2MS44ODA5IDEyNS45NzEgNjEuNjgxNkMxMjUuOSA2MS40ODI0IDEyNS44MDUgNjEuMzA4NiAxMjUuNjg0IDYxLjE2MDJDMTI1LjU2MiA2MS4wMDc4IDEyNS40MTIgNjAuODkwNiAxMjUuMjMyIDYwLjgwODZDMTI1LjA1NyA2MC43MjI3IDEyNC44NDggNjAuNjc5NyAxMjQuNjA1IDYwLjY3OTdDMTI0LjM0OCA2MC42Nzk3IDEyNC4xMjkgNjAuNzM0NCAxMjMuOTQ5IDYwLjg0MzhDMTIzLjc3IDYwLjk1MzEgMTIzLjYyMSA2MS4xMDM1IDEyMy41MDQgNjEuMjk0OUMxMjMuMzkxIDYxLjQ4NjMgMTIzLjMwNyA2MS43MDkgMTIzLjI1MiA2MS45NjI5QzEyMy4xOTcgNjIuMjE2OCAxMjMuMTcgNjIuNDg4MyAxMjMuMTcgNjIuNzc3M1pNMTMyLjYwMiA2NC43Mjg1VjYxLjcwNTFDMTMyLjYwMiA2MS40Nzg1IDEzMi41NjEgNjEuMjgzMiAxMzIuNDc5IDYxLjExOTFDMTMyLjM5NiA2MC45NTUxIDEzMi4yNzEgNjAuODI4MSAxMzIuMTA0IDYwLjczODNDMTMxLjkzOSA2MC42NDg0IDEzMS43MzIgNjAuNjAzNSAxMzEuNDgyIDYwLjYwMzVDMTMxLjI1MiA2MC42MDM1IDEzMS4wNTMgNjAuNjQyNiAxMzAuODg1IDYwLjcyMDdDMTMwLjcxNyA2MC43OTg4IDEzMC41ODYgNjAuOTA0MyAxMzAuNDkyIDYxLjAzNzFDMTMwLjM5OCA2MS4xNjk5IDEzMC4zNTIgNjEuMzIwMyAxMzAuMzUyIDYxLjQ4ODNIMTI4Ljk0NUMxMjguOTQ1IDYxLjIzODMgMTI5LjAwNiA2MC45OTYxIDEyOS4xMjcgNjAuNzYxN0MxMjkuMjQ4IDYwLjUyNzMgMTI5LjQyNCA2MC4zMTg0IDEyOS42NTQgNjAuMTM0OEMxMjkuODg1IDU5Ljk1MTIgMTMwLjE2IDU5LjgwNjYgMTMwLjQ4IDU5LjcwMTJDMTMwLjgwMSA1OS41OTU3IDEzMS4xNiA1OS41NDMgMTMxLjU1OSA1OS41NDNDMTMyLjAzNSA1OS41NDMgMTMyLjQ1NyA1OS42MjMgMTMyLjgyNCA1OS43ODMyQzEzMy4xOTUgNTkuOTQzNCAxMzMuNDg2IDYwLjE4NTUgMTMzLjY5NyA2MC41MDk4QzEzMy45MTIgNjAuODMwMSAxMzQuMDIgNjEuMjMyNCAxMzQuMDIgNjEuNzE2OFY2NC41MzUyQzEzNC4wMiA2NC44MjQyIDEzNC4wMzkgNjUuMDg0IDEzNC4wNzggNjUuMzE0NUMxMzQuMTIxIDY1LjU0MSAxMzQuMTgyIDY1LjczODMgMTM0LjI2IDY1LjkwNjJWNjZIMTMyLjgxMkMxMzIuNzQ2IDY1Ljg0NzcgMTMyLjY5MyA2NS42NTQzIDEzMi42NTQgNjUuNDE5OUMxMzIuNjE5IDY1LjE4MTYgMTMyLjYwMiA2NC45NTEyIDEzMi42MDIgNjQuNzI4NVpNMTMyLjgwNyA2Mi4xNDQ1TDEzMi44MTggNjMuMDE3NkgxMzEuODA1QzEzMS41NDMgNjMuMDE3NiAxMzEuMzEyIDYzLjA0MyAxMzEuMTEzIDYzLjA5MzhDMTMwLjkxNCA2My4xNDA2IDEzMC43NDggNjMuMjEwOSAxMzAuNjE1IDYzLjMwNDdDMTMwLjQ4MiA2My4zOTg0IDEzMC4zODMgNjMuNTExNyAxMzAuMzE2IDYzLjY0NDVDMTMwLjI1IDYzLjc3NzMgMTMwLjIxNyA2My45Mjc3IDEzMC4yMTcgNjQuMDk1N0MxMzAuMjE3IDY0LjI2MzcgMTMwLjI1NiA2NC40MTggMTMwLjMzNCA2NC41NTg2QzEzMC40MTIgNjQuNjk1MyAxMzAuNTI1IDY0LjgwMjcgMTMwLjY3NCA2NC44ODA5QzEzMC44MjYgNjQuOTU5IDEzMS4wMSA2NC45OTggMTMxLjIyNSA2NC45OThDMTMxLjUxNCA2NC45OTggMTMxLjc2NiA2NC45Mzk1IDEzMS45OCA2NC44MjIzQzEzMi4xOTkgNjQuNzAxMiAxMzIuMzcxIDY0LjU1NDcgMTMyLjQ5NiA2NC4zODI4QzEzMi42MjEgNjQuMjA3IDEzMi42ODggNjQuMDQxIDEzMi42OTUgNjMuODg0OEwxMzMuMTUyIDY0LjUxMTdDMTMzLjEwNSA2NC42NzE5IDEzMy4wMjUgNjQuODQzOCAxMzIuOTEyIDY1LjAyNzNDMTMyLjc5OSA2NS4yMTA5IDEzMi42NSA2NS4zODY3IDEzMi40NjcgNjUuNTU0N0MxMzIuMjg3IDY1LjcxODggMTMyLjA3IDY1Ljg1MzUgMTMxLjgxNiA2NS45NTlDMTMxLjU2NiA2Ni4wNjQ1IDEzMS4yNzcgNjYuMTE3MiAxMzAuOTQ5IDY2LjExNzJDMTMwLjUzNSA2Ni4xMTcyIDEzMC4xNjYgNjYuMDM1MiAxMjkuODQyIDY1Ljg3MTFDMTI5LjUxOCA2NS43MDMxIDEyOS4yNjQgNjUuNDc4NSAxMjkuMDggNjUuMTk3M0MxMjguODk2IDY0LjkxMjEgMTI4LjgwNSA2NC41ODk4IDEyOC44MDUgNjQuMjMwNUMxMjguODA1IDYzLjg5NDUgMTI4Ljg2NyA2My41OTc3IDEyOC45OTIgNjMuMzM5OEMxMjkuMTIxIDYzLjA3ODEgMTI5LjMwOSA2Mi44NTk0IDEyOS41NTUgNjIuNjgzNkMxMjkuODA1IDYyLjUwNzggMTMwLjEwOSA2Mi4zNzUgMTMwLjQ2OSA2Mi4yODUyQzEzMC44MjggNjIuMTkxNCAxMzEuMjM4IDYyLjE0NDUgMTMxLjY5OSA2Mi4xNDQ1SDEzMi44MDdaTTEzOC42NTIgNTkuNjYwMlY2MC42OTE0SDEzNS4wNzhWNTkuNjYwMkgxMzguNjUyWk0xMzYuMTA5IDU4LjEwNzRIMTM3LjUyMVY2NC4yNDhDMTM3LjUyMSA2NC40NDM0IDEzNy41NDkgNjQuNTkzOCAxMzcuNjA0IDY0LjY5OTJDMTM3LjY2MiA2NC44MDA4IDEzNy43NDIgNjQuODY5MSAxMzcuODQ0IDY0LjkwNDNDMTM3Ljk0NSA2NC45Mzk1IDEzOC4wNjQgNjQuOTU3IDEzOC4yMDEgNjQuOTU3QzEzOC4yOTkgNjQuOTU3IDEzOC4zOTMgNjQuOTUxMiAxMzguNDgyIDY0LjkzOTVDMTM4LjU3MiA2NC45Mjc3IDEzOC42NDUgNjQuOTE2IDEzOC42OTkgNjQuOTA0M0wxMzguNzA1IDY1Ljk4MjRDMTM4LjU4OCA2Ni4wMTc2IDEzOC40NTEgNjYuMDQ4OCAxMzguMjk1IDY2LjA3NjJDMTM4LjE0MyA2Ni4xMDM1IDEzNy45NjcgNjYuMTE3MiAxMzcuNzY4IDY2LjExNzJDMTM3LjQ0MyA2Ni4xMTcyIDEzNy4xNTYgNjYuMDYwNSAxMzYuOTA2IDY1Ljk0NzNDMTM2LjY1NiA2NS44MzAxIDEzNi40NjEgNjUuNjQwNiAxMzYuMzIgNjUuMzc4OUMxMzYuMTggNjUuMTE3MiAxMzYuMTA5IDY0Ljc2OTUgMTM2LjEwOSA2NC4zMzU5VjU4LjEwNzRaTTE0Mi43ODcgNjYuMTE3MkMxNDIuMzE4IDY2LjExNzIgMTQxLjg5NSA2Ni4wNDEgMTQxLjUxNiA2NS44ODg3QzE0MS4xNDEgNjUuNzMyNCAxNDAuODIgNjUuNTE1NiAxNDAuNTU1IDY1LjIzODNDMTQwLjI5MyA2NC45NjA5IDE0MC4wOTIgNjQuNjM0OCAxMzkuOTUxIDY0LjI1OThDMTM5LjgxMSA2My44ODQ4IDEzOS43NCA2My40ODA1IDEzOS43NCA2My4wNDY5VjYyLjgxMjVDMTM5Ljc0IDYyLjMxNjQgMTM5LjgxMiA2MS44NjcyIDEzOS45NTcgNjEuNDY0OEMxNDAuMTAyIDYxLjA2MjUgMTQwLjMwMyA2MC43MTg4IDE0MC41NjEgNjAuNDMzNkMxNDAuODE4IDYwLjE0NDUgMTQxLjEyMyA1OS45MjM4IDE0MS40NzUgNTkuNzcxNUMxNDEuODI2IDU5LjYxOTEgMTQyLjIwNyA1OS41NDMgMTQyLjYxNyA1OS41NDNDMTQzLjA3IDU5LjU0MyAxNDMuNDY3IDU5LjYxOTEgMTQzLjgwNyA1OS43NzE1QzE0NC4xNDYgNTkuOTIzOCAxNDQuNDI4IDYwLjEzODcgMTQ0LjY1IDYwLjQxNkMxNDQuODc3IDYwLjY4OTUgMTQ1LjA0NSA2MS4wMTU2IDE0NS4xNTQgNjEuMzk0NUMxNDUuMjY4IDYxLjc3MzQgMTQ1LjMyNCA2Mi4xOTE0IDE0NS4zMjQgNjIuNjQ4NFY2My4yNTJIMTQwLjQyNlY2Mi4yMzgzSDE0My45M1Y2Mi4xMjdDMTQzLjkyMiA2MS44NzMgMTQzLjg3MSA2MS42MzQ4IDE0My43NzcgNjEuNDEyMUMxNDMuNjg4IDYxLjE4OTUgMTQzLjU0OSA2MS4wMDk4IDE0My4zNjEgNjAuODczQzE0My4xNzQgNjAuNzM2MyAxNDIuOTI0IDYwLjY2OCAxNDIuNjExIDYwLjY2OEMxNDIuMzc3IDYwLjY2OCAxNDIuMTY4IDYwLjcxODggMTQxLjk4NCA2MC44MjAzQzE0MS44MDUgNjAuOTE4IDE0MS42NTQgNjEuMDYwNSAxNDEuNTMzIDYxLjI0OEMxNDEuNDEyIDYxLjQzNTUgMTQxLjMxOCA2MS42NjIxIDE0MS4yNTIgNjEuOTI3N0MxNDEuMTg5IDYyLjE4OTUgMTQxLjE1OCA2Mi40ODQ0IDE0MS4xNTggNjIuODEyNVY2My4wNDY5QzE0MS4xNTggNjMuMzI0MiAxNDEuMTk1IDYzLjU4MiAxNDEuMjcgNjMuODIwM0MxNDEuMzQ4IDY0LjA1NDcgMTQxLjQ2MSA2NC4yNTk4IDE0MS42MDkgNjQuNDM1NUMxNDEuNzU4IDY0LjYxMTMgMTQxLjkzOCA2NC43NSAxNDIuMTQ4IDY0Ljg1MTZDMTQyLjM1OSA2NC45NDkyIDE0Mi42IDY0Ljk5OCAxNDIuODY5IDY0Ljk5OEMxNDMuMjA5IDY0Ljk5OCAxNDMuNTEyIDY0LjkyOTcgMTQzLjc3NyA2NC43OTNDMTQ0LjA0MyA2NC42NTYyIDE0NC4yNzMgNjQuNDYyOSAxNDQuNDY5IDY0LjIxMjlMMTQ1LjIxMyA2NC45MzM2QzE0NS4wNzYgNjUuMTMyOCAxNDQuODk4IDY1LjMyNDIgMTQ0LjY4IDY1LjUwNzhDMTQ0LjQ2MSA2NS42ODc1IDE0NC4xOTMgNjUuODM0IDE0My44NzcgNjUuOTQ3M0MxNDMuNTY0IDY2LjA2MDUgMTQzLjIwMSA2Ni4xMTcyIDE0Mi43ODcgNjYuMTE3MlpNMTUzLjY4OCA1Ny40Mzk1VjY2SDE1Mi4yNzVWNTkuMTE1MkwxNTAuMTg0IDU5LjgyNDJWNTguNjU4MkwxNTMuNTE4IDU3LjQzOTVIMTUzLjY4OFpNMTYwLjg1MiA2NC42ODc1VjU3SDE2Mi4yN1Y2NkgxNjAuOTg2TDE2MC44NTIgNjQuNjg3NVpNMTU2LjcyNyA2Mi45MDA0VjYyLjc3NzNDMTU2LjcyNyA2Mi4yOTY5IDE1Ni43ODMgNjEuODU5NCAxNTYuODk2IDYxLjQ2NDhDMTU3LjAxIDYxLjA2NjQgMTU3LjE3NCA2MC43MjQ2IDE1Ny4zODkgNjAuNDM5NUMxNTcuNjA0IDYwLjE1MDQgMTU3Ljg2NSA1OS45Mjk3IDE1OC4xNzQgNTkuNzc3M0MxNTguNDgyIDU5LjYyMTEgMTU4LjgzIDU5LjU0MyAxNTkuMjE3IDU5LjU0M0MxNTkuNiA1OS41NDMgMTU5LjkzNiA1OS42MTcyIDE2MC4yMjUgNTkuNzY1NkMxNjAuNTE0IDU5LjkxNDEgMTYwLjc2IDYwLjEyNyAxNjAuOTYzIDYwLjQwNDNDMTYxLjE2NiA2MC42Nzc3IDE2MS4zMjggNjEuMDA1OSAxNjEuNDQ5IDYxLjM4ODdDMTYxLjU3IDYxLjc2NzYgMTYxLjY1NiA2Mi4xODk1IDE2MS43MDcgNjIuNjU0M1Y2My4wNDY5QzE2MS42NTYgNjMuNSAxNjEuNTcgNjMuOTE0MSAxNjEuNDQ5IDY0LjI4OTFDMTYxLjMyOCA2NC42NjQxIDE2MS4xNjYgNjQuOTg4MyAxNjAuOTYzIDY1LjI2MTdDMTYwLjc2IDY1LjUzNTIgMTYwLjUxMiA2NS43NDYxIDE2MC4yMTkgNjUuODk0NUMxNTkuOTMgNjYuMDQzIDE1OS41OTIgNjYuMTE3MiAxNTkuMjA1IDY2LjExNzJDMTU4LjgyMiA2Ni4xMTcyIDE1OC40NzcgNjYuMDM3MSAxNTguMTY4IDY1Ljg3N0MxNTcuODYzIDY1LjcxNjggMTU3LjYwNCA2NS40OTIyIDE1Ny4zODkgNjUuMjAzMUMxNTcuMTc0IDY0LjkxNDEgMTU3LjAxIDY0LjU3NDIgMTU2Ljg5NiA2NC4xODM2QzE1Ni43ODMgNjMuNzg5MSAxNTYuNzI3IDYzLjM2MTMgMTU2LjcyNyA2Mi45MDA0Wk0xNTguMTM5IDYyLjc3NzNWNjIuOTAwNEMxNTguMTM5IDYzLjE4OTUgMTU4LjE2NCA2My40NTkgMTU4LjIxNSA2My43MDlDMTU4LjI3IDYzLjk1OSAxNTguMzU0IDY0LjE3OTcgMTU4LjQ2NyA2NC4zNzExQzE1OC41OCA2NC41NTg2IDE1OC43MjcgNjQuNzA3IDE1OC45MDYgNjQuODE2NEMxNTkuMDkgNjQuOTIxOSAxNTkuMzA5IDY0Ljk3NDYgMTU5LjU2MiA2NC45NzQ2QzE1OS44ODMgNjQuOTc0NiAxNjAuMTQ2IDY0LjkwNDMgMTYwLjM1NCA2NC43NjM3QzE2MC41NjEgNjQuNjIzIDE2MC43MjMgNjQuNDMzNiAxNjAuODQgNjQuMTk1M0MxNjAuOTYxIDYzLjk1MzEgMTYxLjA0MyA2My42ODM2IDE2MS4wODYgNjMuMzg2N1Y2Mi4zMjYyQzE2MS4wNjIgNjIuMDk1NyAxNjEuMDE0IDYxLjg4MDkgMTYwLjkzOSA2MS42ODE2QzE2MC44NjkgNjEuNDgyNCAxNjAuNzczIDYxLjMwODYgMTYwLjY1MiA2MS4xNjAyQzE2MC41MzEgNjEuMDA3OCAxNjAuMzgxIDYwLjg5MDYgMTYwLjIwMSA2MC44MDg2QzE2MC4wMjUgNjAuNzIyNyAxNTkuODE2IDYwLjY3OTcgMTU5LjU3NCA2MC42Nzk3QzE1OS4zMTYgNjAuNjc5NyAxNTkuMDk4IDYwLjczNDQgMTU4LjkxOCA2MC44NDM4QzE1OC43MzggNjAuOTUzMSAxNTguNTkgNjEuMTAzNSAxNTguNDczIDYxLjI5NDlDMTU4LjM1OSA2MS40ODYzIDE1OC4yNzUgNjEuNzA5IDE1OC4yMjEgNjEuOTYyOUMxNTguMTY2IDYyLjIxNjggMTU4LjEzOSA2Mi40ODgzIDE1OC4xMzkgNjIuNzc3M1pNMTcwLjgwOSA2NC43Mjg1VjYxLjcwNTFDMTcwLjgwOSA2MS40Nzg1IDE3MC43NjggNjEuMjgzMiAxNzAuNjg2IDYxLjExOTFDMTcwLjYwNCA2MC45NTUxIDE3MC40NzkgNjAuODI4MSAxNzAuMzExIDYwLjczODNDMTcwLjE0NiA2MC42NDg0IDE2OS45MzkgNjAuNjAzNSAxNjkuNjg5IDYwLjYwMzVDMTY5LjQ1OSA2MC42MDM1IDE2OS4yNiA2MC42NDI2IDE2OS4wOTIgNjAuNzIwN0MxNjguOTI0IDYwLjc5ODggMTY4Ljc5MyA2MC45MDQzIDE2OC42OTkgNjEuMDM3MUMxNjguNjA1IDYxLjE2OTkgMTY4LjU1OSA2MS4zMjAzIDE2OC41NTkgNjEuNDg4M0gxNjcuMTUyQzE2Ny4xNTIgNjEuMjM4MyAxNjcuMjEzIDYwLjk5NjEgMTY3LjMzNCA2MC43NjE3QzE2Ny40NTUgNjAuNTI3MyAxNjcuNjMxIDYwLjMxODQgMTY3Ljg2MSA2MC4xMzQ4QzE2OC4wOTIgNTkuOTUxMiAxNjguMzY3IDU5LjgwNjYgMTY4LjY4OCA1OS43MDEyQzE2OS4wMDggNTkuNTk1NyAxNjkuMzY3IDU5LjU0MyAxNjkuNzY2IDU5LjU0M0MxNzAuMjQyIDU5LjU0MyAxNzAuNjY0IDU5LjYyMyAxNzEuMDMxIDU5Ljc4MzJDMTcxLjQwMiA1OS45NDM0IDE3MS42OTMgNjAuMTg1NSAxNzEuOTA0IDYwLjUwOThDMTcyLjExOSA2MC44MzAxIDE3Mi4yMjcgNjEuMjMyNCAxNzIuMjI3IDYxLjcxNjhWNjQuNTM1MkMxNzIuMjI3IDY0LjgyNDIgMTcyLjI0NiA2NS4wODQgMTcyLjI4NSA2NS4zMTQ1QzE3Mi4zMjggNjUuNTQxIDE3Mi4zODkgNjUuNzM4MyAxNzIuNDY3IDY1LjkwNjJWNjZIMTcxLjAyQzE3MC45NTMgNjUuODQ3NyAxNzAuOSA2NS42NTQzIDE3MC44NjEgNjUuNDE5OUMxNzAuODI2IDY1LjE4MTYgMTcwLjgwOSA2NC45NTEyIDE3MC44MDkgNjQuNzI4NVpNMTcxLjAxNCA2Mi4xNDQ1TDE3MS4wMjUgNjMuMDE3NkgxNzAuMDEyQzE2OS43NSA2My4wMTc2IDE2OS41MiA2My4wNDMgMTY5LjMyIDYzLjA5MzhDMTY5LjEyMSA2My4xNDA2IDE2OC45NTUgNjMuMjEwOSAxNjguODIyIDYzLjMwNDdDMTY4LjY4OSA2My4zOTg0IDE2OC41OSA2My41MTE3IDE2OC41MjMgNjMuNjQ0NUMxNjguNDU3IDYzLjc3NzMgMTY4LjQyNCA2My45Mjc3IDE2OC40MjQgNjQuMDk1N0MxNjguNDI0IDY0LjI2MzcgMTY4LjQ2MyA2NC40MTggMTY4LjU0MSA2NC41NTg2QzE2OC42MTkgNjQuNjk1MyAxNjguNzMyIDY0LjgwMjcgMTY4Ljg4MSA2NC44ODA5QzE2OS4wMzMgNjQuOTU5IDE2OS4yMTcgNjQuOTk4IDE2OS40MzIgNjQuOTk4QzE2OS43MjEgNjQuOTk4IDE2OS45NzMgNjQuOTM5NSAxNzAuMTg4IDY0LjgyMjNDMTcwLjQwNiA2NC43MDEyIDE3MC41NzggNjQuNTU0NyAxNzAuNzAzIDY0LjM4MjhDMTcwLjgyOCA2NC4yMDcgMTcwLjg5NSA2NC4wNDEgMTcwLjkwMiA2My44ODQ4TDE3MS4zNTkgNjQuNTExN0MxNzEuMzEyIDY0LjY3MTkgMTcxLjIzMiA2NC44NDM4IDE3MS4xMTkgNjUuMDI3M0MxNzEuMDA2IDY1LjIxMDkgMTcwLjg1NyA2NS4zODY3IDE3MC42NzQgNjUuNTU0N0MxNzAuNDk0IDY1LjcxODggMTcwLjI3NyA2NS44NTM1IDE3MC4wMjMgNjUuOTU5QzE2OS43NzMgNjYuMDY0NSAxNjkuNDg0IDY2LjExNzIgMTY5LjE1NiA2Ni4xMTcyQzE2OC43NDIgNjYuMTE3MiAxNjguMzczIDY2LjAzNTIgMTY4LjA0OSA2NS44NzExQzE2Ny43MjUgNjUuNzAzMSAxNjcuNDcxIDY1LjQ3ODUgMTY3LjI4NyA2NS4xOTczQzE2Ny4xMDQgNjQuOTEyMSAxNjcuMDEyIDY0LjU4OTggMTY3LjAxMiA2NC4yMzA1QzE2Ny4wMTIgNjMuODk0NSAxNjcuMDc0IDYzLjU5NzcgMTY3LjE5OSA2My4zMzk4QzE2Ny4zMjggNjMuMDc4MSAxNjcuNTE2IDYyLjg1OTQgMTY3Ljc2MiA2Mi42ODM2QzE2OC4wMTIgNjIuNTA3OCAxNjguMzE2IDYyLjM3NSAxNjguNjc2IDYyLjI4NTJDMTY5LjAzNSA2Mi4xOTE0IDE2OS40NDUgNjIuMTQ0NSAxNjkuOTA2IDYyLjE0NDVIMTcxLjAxNFpNMTc4LjAxNCA1OS42NjAySDE3OS4yOTdWNjUuODI0MkMxNzkuMjk3IDY2LjM5NDUgMTc5LjE3NiA2Ni44Nzg5IDE3OC45MzQgNjcuMjc3M0MxNzguNjkxIDY3LjY3NTggMTc4LjM1NCA2Ny45Nzg1IDE3Ny45MiA2OC4xODU1QzE3Ny40ODYgNjguMzk2NSAxNzYuOTg0IDY4LjUwMiAxNzYuNDE0IDY4LjUwMkMxNzYuMTcyIDY4LjUwMiAxNzUuOTAyIDY4LjQ2NjggMTc1LjYwNSA2OC4zOTY1QzE3NS4zMTIgNjguMzI2MiAxNzUuMDI3IDY4LjIxMjkgMTc0Ljc1IDY4LjA1NjZDMTc0LjQ3NyA2Ny45MDQzIDE3NC4yNDggNjcuNzAzMSAxNzQuMDY0IDY3LjQ1MzFMMTc0LjcyNyA2Ni42MjExQzE3NC45NTMgNjYuODkwNiAxNzUuMjAzIDY3LjA4NzkgMTc1LjQ3NyA2Ny4yMTI5QzE3NS43NSA2Ny4zMzc5IDE3Ni4wMzcgNjcuNDAwNCAxNzYuMzM4IDY3LjQwMDRDMTc2LjY2MiA2Ny40MDA0IDE3Ni45MzggNjcuMzM5OCAxNzcuMTY0IDY3LjIxODhDMTc3LjM5NSA2Ny4xMDE2IDE3Ny41NzIgNjYuOTI3NyAxNzcuNjk3IDY2LjY5NzNDMTc3LjgyMiA2Ni40NjY4IDE3Ny44ODUgNjYuMTg1NSAxNzcuODg1IDY1Ljg1MzVWNjEuMDk1N0wxNzguMDE0IDU5LjY2MDJaTTE3My43MDcgNjIuOTAwNFY2Mi43NzczQzE3My43MDcgNjIuMjk2OSAxNzMuNzY2IDYxLjg1OTQgMTczLjg4MyA2MS40NjQ4QzE3NCA2MS4wNjY0IDE3NC4xNjggNjAuNzI0NiAxNzQuMzg3IDYwLjQzOTVDMTc0LjYwNSA2MC4xNTA0IDE3NC44NzEgNTkuOTI5NyAxNzUuMTg0IDU5Ljc3NzNDMTc1LjQ5NiA1OS42MjExIDE3NS44NSA1OS41NDMgMTc2LjI0NCA1OS41NDNDMTc2LjY1NCA1OS41NDMgMTc3LjAwNCA1OS42MTcyIDE3Ny4yOTMgNTkuNzY1NkMxNzcuNTg2IDU5LjkxNDEgMTc3LjgzIDYwLjEyNyAxNzguMDI1IDYwLjQwNDNDMTc4LjIyMSA2MC42Nzc3IDE3OC4zNzMgNjEuMDA1OSAxNzguNDgyIDYxLjM4ODdDMTc4LjU5NiA2MS43Njc2IDE3OC42OCA2Mi4xODk1IDE3OC43MzQgNjIuNjU0M1Y2My4wNDY5QzE3OC42ODQgNjMuNSAxNzguNTk4IDYzLjkxNDEgMTc4LjQ3NyA2NC4yODkxQzE3OC4zNTUgNjQuNjY0MSAxNzguMTk1IDY0Ljk4ODMgMTc3Ljk5NiA2NS4yNjE3QzE3Ny43OTcgNjUuNTM1MiAxNzcuNTUxIDY1Ljc0NjEgMTc3LjI1OCA2NS44OTQ1QzE3Ni45NjkgNjYuMDQzIDE3Ni42MjcgNjYuMTE3MiAxNzYuMjMyIDY2LjExNzJDMTc1Ljg0NiA2Ni4xMTcyIDE3NS40OTYgNjYuMDM3MSAxNzUuMTg0IDY1Ljg3N0MxNzQuODc1IDY1LjcxNjggMTc0LjYwOSA2NS40OTIyIDE3NC4zODcgNjUuMjAzMUMxNzQuMTY4IDY0LjkxNDEgMTc0IDY0LjU3NDIgMTczLjg4MyA2NC4xODM2QzE3My43NjYgNjMuNzg5MSAxNzMuNzA3IDYzLjM2MTMgMTczLjcwNyA2Mi45MDA0Wk0xNzUuMTE5IDYyLjc3NzNWNjIuOTAwNEMxNzUuMTE5IDYzLjE4OTUgMTc1LjE0NiA2My40NTkgMTc1LjIwMSA2My43MDlDMTc1LjI2IDYzLjk1OSAxNzUuMzQ4IDY0LjE3OTcgMTc1LjQ2NSA2NC4zNzExQzE3NS41ODYgNjQuNTU4NiAxNzUuNzM4IDY0LjcwNyAxNzUuOTIyIDY0LjgxNjRDMTc2LjEwOSA2NC45MjE5IDE3Ni4zMyA2NC45NzQ2IDE3Ni41ODQgNjQuOTc0NkMxNzYuOTE2IDY0Ljk3NDYgMTc3LjE4OCA2NC45MDQzIDE3Ny4zOTggNjQuNzYzN0MxNzcuNjEzIDY0LjYyMyAxNzcuNzc3IDY0LjQzMzYgMTc3Ljg5MSA2NC4xOTUzQzE3OC4wMDggNjMuOTUzMSAxNzguMDkgNjMuNjgzNiAxNzguMTM3IDYzLjM4NjdWNjIuMzI2MkMxNzguMTEzIDYyLjA5NTcgMTc4LjA2NCA2MS44ODA5IDE3Ny45OSA2MS42ODE2QzE3Ny45MiA2MS40ODI0IDE3Ny44MjQgNjEuMzA4NiAxNzcuNzAzIDYxLjE2MDJDMTc3LjU4MiA2MS4wMDc4IDE3Ny40MyA2MC44OTA2IDE3Ny4yNDYgNjAuODA4NkMxNzcuMDYyIDYwLjcyMjcgMTc2Ljg0NiA2MC42Nzk3IDE3Ni41OTYgNjAuNjc5N0MxNzYuMzQyIDYwLjY3OTcgMTc2LjEyMSA2MC43MzQ0IDE3NS45MzQgNjAuODQzOEMxNzUuNzQ2IDYwLjk1MzEgMTc1LjU5MiA2MS4xMDM1IDE3NS40NzEgNjEuMjk0OUMxNzUuMzU0IDYxLjQ4NjMgMTc1LjI2NiA2MS43MDkgMTc1LjIwNyA2MS45NjI5QzE3NS4xNDggNjIuMjE2OCAxNzUuMTE5IDYyLjQ4ODMgMTc1LjExOSA2Mi43NzczWk0xODAuNzQyIDYyLjkwMDRWNjIuNzY1NkMxODAuNzQyIDYyLjMwODYgMTgwLjgwOSA2MS44ODQ4IDE4MC45NDEgNjEuNDk0MUMxODEuMDc0IDYxLjA5OTYgMTgxLjI2NiA2MC43NTc4IDE4MS41MTYgNjAuNDY4OEMxODEuNzcgNjAuMTc1OCAxODIuMDc4IDU5Ljk0OTIgMTgyLjQ0MSA1OS43ODkxQzE4Mi44MDkgNTkuNjI1IDE4My4yMjMgNTkuNTQzIDE4My42ODQgNTkuNTQzQzE4NC4xNDggNTkuNTQzIDE4NC41NjIgNTkuNjI1IDE4NC45MjYgNTkuNzg5MUMxODUuMjkzIDU5Ljk0OTIgMTg1LjYwNCA2MC4xNzU4IDE4NS44NTcgNjAuNDY4OEMxODYuMTExIDYwLjc1NzggMTg2LjMwNSA2MS4wOTk2IDE4Ni40MzggNjEuNDk0MUMxODYuNTcgNjEuODg0OCAxODYuNjM3IDYyLjMwODYgMTg2LjYzNyA2Mi43NjU2VjYyLjkwMDRDMTg2LjYzNyA2My4zNTc0IDE4Ni41NyA2My43ODEyIDE4Ni40MzggNjQuMTcxOUMxODYuMzA1IDY0LjU2MjUgMTg2LjExMSA2NC45MDQzIDE4NS44NTcgNjUuMTk3M0MxODUuNjA0IDY1LjQ4NjMgMTg1LjI5NSA2NS43MTI5IDE4NC45MzIgNjUuODc3QzE4NC41NjggNjYuMDM3MSAxODQuMTU2IDY2LjExNzIgMTgzLjY5NSA2Ni4xMTcyQzE4My4yMyA2Ni4xMTcyIDE4Mi44MTQgNjYuMDM3MSAxODIuNDQ3IDY1Ljg3N0MxODIuMDg0IDY1LjcxMjkgMTgxLjc3NSA2NS40ODYzIDE4MS41MjEgNjUuMTk3M0MxODEuMjY4IDY0LjkwNDMgMTgxLjA3NCA2NC41NjI1IDE4MC45NDEgNjQuMTcxOUMxODAuODA5IDYzLjc4MTIgMTgwLjc0MiA2My4zNTc0IDE4MC43NDIgNjIuOTAwNFpNMTgyLjE1NCA2Mi43NjU2VjYyLjkwMDRDMTgyLjE1NCA2My4xODU1IDE4Mi4xODQgNjMuNDU1MSAxODIuMjQyIDYzLjcwOUMxODIuMzAxIDYzLjk2MjkgMTgyLjM5MyA2NC4xODU1IDE4Mi41MTggNjQuMzc3QzE4Mi42NDMgNjQuNTY4NCAxODIuODAzIDY0LjcxODggMTgyLjk5OCA2NC44MjgxQzE4My4xOTMgNjQuOTM3NSAxODMuNDI2IDY0Ljk5MjIgMTgzLjY5NSA2NC45OTIyQzE4My45NTcgNjQuOTkyMiAxODQuMTg0IDY0LjkzNzUgMTg0LjM3NSA2NC44MjgxQzE4NC41NyA2NC43MTg4IDE4NC43MyA2NC41Njg0IDE4NC44NTUgNjQuMzc3QzE4NC45OCA2NC4xODU1IDE4NS4wNzIgNjMuOTYyOSAxODUuMTMxIDYzLjcwOUMxODUuMTkzIDYzLjQ1NTEgMTg1LjIyNSA2My4xODU1IDE4NS4yMjUgNjIuOTAwNFY2Mi43NjU2QzE4NS4yMjUgNjIuNDg0NCAxODUuMTkzIDYyLjIxODggMTg1LjEzMSA2MS45Njg4QzE4NS4wNzIgNjEuNzE0OCAxODQuOTc5IDYxLjQ5MDIgMTg0Ljg1IDYxLjI5NDlDMTg0LjcyNSA2MS4wOTk2IDE4NC41NjQgNjAuOTQ3MyAxODQuMzY5IDYwLjgzNzlDMTg0LjE3OCA2MC43MjQ2IDE4My45NDkgNjAuNjY4IDE4My42ODQgNjAuNjY4QzE4My40MTggNjAuNjY4IDE4My4xODggNjAuNzI0NiAxODIuOTkyIDYwLjgzNzlDMTgyLjgwMSA2MC45NDczIDE4Mi42NDMgNjEuMDk5NiAxODIuNTE4IDYxLjI5NDlDMTgyLjM5MyA2MS40OTAyIDE4Mi4zMDEgNjEuNzE0OCAxODIuMjQyIDYxLjk2ODhDMTgyLjE4NCA2Mi4yMTg4IDE4Mi4xNTQgNjIuNDg0NCAxODIuMTU0IDYyLjc2NTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjM4Ii8+CjxwYXRoIGQ9Ik0yODMuNTc0IDYzLjEyNVY2OEgyNTguNzkzVjYzLjgxMDVMMjcwLjgyOCA1MC42ODM2QzI3Mi4xNDggNDkuMTk0IDI3My4xODkgNDcuOTA3NiAyNzMuOTUxIDQ2LjgyNDJDMjc0LjcxMyA0NS43NDA5IDI3NS4yNDYgNDQuNzY3NiAyNzUuNTUxIDQzLjkwNDNDMjc1Ljg3MiA0My4wMjQxIDI3Ni4wMzMgNDIuMTY5MyAyNzYuMDMzIDQxLjMzOThDMjc2LjAzMyA0MC4xNzE5IDI3NS44MTMgMzkuMTQ3OCAyNzUuMzczIDM4LjI2NzZDMjc0Ljk1IDM3LjM3MDQgMjc0LjMyNCAzNi42NjggMjczLjQ5NCAzNi4xNjAyQzI3Mi42NjUgMzUuNjM1NCAyNzEuNjU4IDM1LjM3MyAyNzAuNDczIDM1LjM3M0MyNjkuMTAyIDM1LjM3MyAyNjcuOTUxIDM1LjY2OTMgMjY3LjAyIDM2LjI2MTdDMjY2LjA4OSAzNi44NTQyIDI2NS4zODYgMzcuNjc1MSAyNjQuOTEyIDM4LjcyNDZDMjY0LjQzOCAzOS43NTcyIDI2NC4yMDEgNDAuOTQyMSAyNjQuMjAxIDQyLjI3OTNIMjU4LjA4MkMyNTguMDgyIDQwLjEyOTYgMjU4LjU3MyAzOC4xNjYgMjU5LjU1NSAzNi4zODg3QzI2MC41MzYgMzQuNTk0NCAyNjEuOTU4IDMzLjE3MjUgMjYzLjgyIDMyLjEyM0MyNjUuNjgyIDMxLjA1NjYgMjY3LjkyNSAzMC41MjM0IDI3MC41NDkgMzAuNTIzNEMyNzMuMDIgMzAuNTIzNCAyNzUuMTE5IDMwLjkzODIgMjc2Ljg0NiAzMS43Njc2QzI3OC41NzIgMzIuNTk3IDI3OS44ODQgMzMuNzczNCAyODAuNzgxIDM1LjI5NjlDMjgxLjY5NSAzNi44MjAzIDI4Mi4xNTIgMzguNjIzIDI4Mi4xNTIgNDAuNzA1MUMyODIuMTUyIDQxLjg1NjEgMjgxLjk2NiA0Mi45OTg3IDI4MS41OTQgNDQuMTMyOEMyODEuMjIxIDQ1LjI2NjkgMjgwLjY4OCA0Ni40MDEgMjc5Ljk5NCA0Ny41MzUyQzI3OS4zMTcgNDguNjUyMyAyNzguNTEzIDQ5Ljc3OCAyNzcuNTgyIDUwLjkxMjFDMjc2LjY1MSA1Mi4wMjkzIDI3NS42MjcgNTMuMTYzNCAyNzQuNTEgNTQuMzE0NUwyNjYuNTEyIDYzLjEyNUgyODMuNTc0Wk0zMTIuMjE5IDYzLjEyNVY2OEgyODcuNDM4VjYzLjgxMDVMMjk5LjQ3MyA1MC42ODM2QzMwMC43OTMgNDkuMTk0IDMwMS44MzQgNDcuOTA3NiAzMDIuNTk2IDQ2LjgyNDJDMzAzLjM1OCA0NS43NDA5IDMwMy44OTEgNDQuNzY3NiAzMDQuMTk1IDQzLjkwNDNDMzA0LjUxNyA0My4wMjQxIDMwNC42NzggNDIuMTY5MyAzMDQuNjc4IDQxLjMzOThDMzA0LjY3OCA0MC4xNzE5IDMwNC40NTggMzkuMTQ3OCAzMDQuMDE4IDM4LjI2NzZDMzAzLjU5NSAzNy4zNzA0IDMwMi45NjggMzYuNjY4IDMwMi4xMzkgMzYuMTYwMkMzMDEuMzA5IDM1LjYzNTQgMzAwLjMwMiAzNS4zNzMgMjk5LjExNyAzNS4zNzNDMjk3Ljc0NiAzNS4zNzMgMjk2LjU5NSAzNS42NjkzIDI5NS42NjQgMzYuMjYxN0MyOTQuNzMzIDM2Ljg1NDIgMjk0LjAzMSAzNy42NzUxIDI5My41NTcgMzguNzI0NkMyOTMuMDgzIDM5Ljc1NzIgMjkyLjg0NiA0MC45NDIxIDI5Mi44NDYgNDIuMjc5M0gyODYuNzI3QzI4Ni43MjcgNDAuMTI5NiAyODcuMjE4IDM4LjE2NiAyODguMTk5IDM2LjM4ODdDMjg5LjE4MSAzNC41OTQ0IDI5MC42MDMgMzMuMTcyNSAyOTIuNDY1IDMyLjEyM0MyOTQuMzI3IDMxLjA1NjYgMjk2LjU3IDMwLjUyMzQgMjk5LjE5NCAzMC41MjM0QzMwMS42NjUgMzAuNTIzNCAzMDMuNzY0IDMwLjkzODIgMzA1LjQ5IDMxLjc2NzZDMzA3LjIxNyAzMi41OTcgMzA4LjUyOSAzMy43NzM0IDMwOS40MjYgMzUuMjk2OUMzMTAuMzQgMzYuODIwMyAzMTAuNzk3IDM4LjYyMyAzMTAuNzk3IDQwLjcwNTFDMzEwLjc5NyA0MS44NTYxIDMxMC42MTEgNDIuOTk4NyAzMTAuMjM4IDQ0LjEzMjhDMzA5Ljg2NiA0NS4yNjY5IDMwOS4zMzMgNDYuNDAxIDMwOC42MzkgNDcuNTM1MkMzMDcuOTYyIDQ4LjY1MjMgMzA3LjE1OCA0OS43NzggMzA2LjIyNyA1MC45MTIxQzMwNS4yOTYgNTIuMDI5MyAzMDQuMjcyIDUzLjE2MzQgMzAzLjE1NCA1NC4zMTQ1TDI5NS4xNTYgNjMuMTI1SDMxMi4yMTlaTTMxNi41NjUgMzcuMzAyN0MzMTYuNTY1IDM2LjA2NzEgMzE2Ljg2OSAzNC45MzI5IDMxNy40NzkgMzMuOTAwNEMzMTguMDg4IDMyLjg2NzggMzE4LjkwMSAzMi4wNDY5IDMxOS45MTYgMzEuNDM3NUMzMjAuOTQ5IDMwLjgxMTIgMzIyLjA2NiAzMC40OTggMzIzLjI2OCAzMC40OThDMzI0LjQ4NyAzMC40OTggMzI1LjU5NSAzMC44MTEyIDMyNi41OTQgMzEuNDM3NUMzMjcuNTkzIDMyLjA0NjkgMzI4LjM4OCAzMi44Njc4IDMyOC45ODEgMzMuOTAwNEMzMjkuNTkgMzQuOTMyOSAzMjkuODk1IDM2LjA2NzEgMzI5Ljg5NSAzNy4zMDI3QzMyOS44OTUgMzguNTM4NCAzMjkuNTkgMzkuNjcyNSAzMjguOTgxIDQwLjcwNTFDMzI4LjM4OCA0MS43MjA3IDMyNy41OTMgNDIuNTI0NyAzMjYuNTk0IDQzLjExNzJDMzI1LjU5NSA0My43MDk2IDMyNC40ODcgNDQuMDA1OSAzMjMuMjY4IDQ0LjAwNTlDMzIyLjA2NiA0NC4wMDU5IDMyMC45NDkgNDMuNzA5NiAzMTkuOTE2IDQzLjExNzJDMzE4LjkwMSA0Mi41MjQ3IDMxOC4wODggNDEuNzIwNyAzMTcuNDc5IDQwLjcwNTFDMzE2Ljg2OSAzOS42NzI1IDMxNi41NjUgMzguNTM4NCAzMTYuNTY1IDM3LjMwMjdaTTMxOS45OTMgMzcuMzAyN0MzMTkuOTkzIDM4LjIxNjggMzIwLjMxNCAzOC45ODcgMzIwLjk1NyAzOS42MTMzQzMyMS42MDEgNDAuMjIyNyAzMjIuMzcxIDQwLjUyNzMgMzIzLjI2OCA0MC41MjczQzMyNC4xNjUgNDAuNTI3MyAzMjQuOTE4IDQwLjIyMjcgMzI1LjUyOCAzOS42MTMzQzMyNi4xMzcgMzkuMDAzOSAzMjYuNDQyIDM4LjIzMzcgMzI2LjQ0MiAzNy4zMDI3QzMyNi40NDIgMzYuMzU0OCAzMjYuMTM3IDM1LjU2NzcgMzI1LjUyOCAzNC45NDE0QzMyNC45MTggMzQuMzE1MSAzMjQuMTY1IDM0LjAwMiAzMjMuMjY4IDM0LjAwMkMzMjIuMzcxIDM0LjAwMiAzMjEuNjAxIDM0LjMxNTEgMzIwLjk1NyAzNC45NDE0QzMyMC4zMTQgMzUuNTY3NyAzMTkuOTkzIDM2LjM1NDggMzE5Ljk5MyAzNy4zMDI3Wk0zNTcuODc5IDU1Ljk2NDhIMzY0LjIyN0MzNjQuMDI0IDU4LjM4NTQgMzYzLjM0NyA2MC41NDM2IDM2Mi4xOTYgNjIuNDM5NUMzNjEuMDQ1IDY0LjMxODQgMzU5LjQyOCA2NS43OTk1IDM1Ny4zNDYgNjYuODgyOEMzNTUuMjY0IDY3Ljk2NjEgMzUyLjczNCA2OC41MDc4IDM0OS43NTQgNjguNTA3OEMzNDcuNDY5IDY4LjUwNzggMzQ1LjQxMyA2OC4xMDE2IDM0My41ODQgNjcuMjg5MUMzNDEuNzU2IDY2LjQ1OTYgMzQwLjE5MSA2NS4yOTE3IDMzOC44ODcgNjMuNzg1MkMzMzcuNTg0IDYyLjI2MTcgMzM2LjU4NSA2MC40MjUxIDMzNS44OTEgNTguMjc1NEMzMzUuMjE0IDU2LjEyNTcgMzM0Ljg3NSA1My43MjIgMzM0Ljg3NSA1MS4wNjQ1VjQ3Ljk5MjJDMzM0Ljg3NSA0NS4zMzQ2IDMzNS4yMjIgNDIuOTMxIDMzNS45MTYgNDAuNzgxMkMzMzYuNjI3IDM4LjYzMTUgMzM3LjY0MyAzNi43OTQ5IDMzOC45NjMgMzUuMjcxNUMzNDAuMjg0IDMzLjczMTEgMzQxLjg2NiAzMi41NTQ3IDM0My43MTEgMzEuNzQyMkMzNDUuNTczIDMwLjkyOTcgMzQ3LjY2NCAzMC41MjM0IDM0OS45ODMgMzAuNTIzNEMzNTIuOTI4IDMwLjUyMzQgMzU1LjQxNiAzMS4wNjUxIDM1Ny40NDggMzIuMTQ4NEMzNTkuNDc5IDMzLjIzMTggMzYxLjA1MyAzNC43Mjk4IDM2Mi4xNyAzNi42NDI2QzM2My4zMDUgMzguNTU1MyAzNjMuOTk5IDQwLjc0NzQgMzY0LjI1MiA0My4yMTg4SDM1Ny45MDVDMzU3LjczNSA0MS42Mjc2IDM1Ny4zNjMgNDAuMjY1IDM1Ni43ODggMzkuMTMwOUMzNTYuMjI5IDM3Ljk5NjcgMzU1LjQgMzcuMTMzNSAzNTQuMjk5IDM2LjU0MUMzNTMuMTk5IDM1LjkzMTYgMzUxLjc2IDM1LjYyNyAzNDkuOTgzIDM1LjYyN0MzNDguNTI3IDM1LjYyNyAzNDcuMjU4IDM1Ljg5NzggMzQ2LjE3NCAzNi40Mzk1QzM0NS4wOTEgMzYuOTgxMSAzNDQuMTg1IDM3Ljc3NjcgMzQzLjQ1NyAzOC44MjYyQzM0Mi43MyAzOS44NzU3IDM0Mi4xOCA0MS4xNzA2IDM0MS44MDcgNDIuNzEwOUMzNDEuNDUyIDQ0LjIzNDQgMzQxLjI3NCA0NS45Nzc5IDM0MS4yNzQgNDcuOTQxNFY1MS4wNjQ1QzM0MS4yNzQgNTIuOTI2NCAzNDEuNDM1IDU0LjYxOTEgMzQxLjc1NiA1Ni4xNDI2QzM0Mi4wOTUgNTcuNjQ5MSAzNDIuNjAzIDU4Ljk0NCAzNDMuMjggNjAuMDI3M0MzNDMuOTc0IDYxLjExMDcgMzQ0Ljg1NCA2MS45NDg2IDM0NS45MiA2Mi41NDFDMzQ2Ljk4NyA2My4xMzM1IDM0OC4yNjUgNjMuNDI5NyAzNDkuNzU0IDYzLjQyOTdDMzUxLjU2NiA2My40Mjk3IDM1My4wMyA2My4xNDE5IDM1NC4xNDcgNjIuNTY2NEMzNTUuMjgxIDYxLjk5MDkgMzU2LjEzNiA2MS4xNTMgMzU2LjcxMSA2MC4wNTI3QzM1Ny4zMDQgNTguOTM1NSAzNTcuNjkzIDU3LjU3MjkgMzU3Ljg3OSA1NS45NjQ4WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC44NyIvPgo8L2c+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfMTI0Nl80NDQ0NyIgeD0iMCIgeT0iMCIgd2lkdGg9IjM5OSIgaGVpZ2h0PSIxMDgiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldCBkeT0iNCIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSI0Ii8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4wNCAwIi8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0iZWZmZWN0MV9kcm9wU2hhZG93XzEyNDZfNDQ0NDciLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3dfMTI0Nl80NDQ0NyIgcmVzdWx0PSJzaGFwZSIvPgo8L2ZpbHRlcj4KPC9kZWZzPgo8L3N2Zz4K", "description": "Designed to display single value of the selected attribute or timeseries data. Widget styles are customizable.", "descriptor": { "type": "latest", @@ -259,10 +259,10 @@ "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n horizontal: true,\n previewWidth: '420px',\n previewHeight: '130px'\n };\n};\n\nself.onDestroy = function() {\n};\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n horizontal: true,\n previewWidth: '420px',\n previewHeight: '130px',\n absoluteHeader: true\n };\n};\n\nself.onDestroy = function() {\n};\n", "settingsSchema": "", "dataKeySettingsSchema": "", - "settingsDirective": "", + "settingsDirective": "tb-value-card-widget-settings", "hasBasicMode": true, "basicModeDirective": "tb-value-card-basic-config", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"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}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Horizontal value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}" diff --git a/ui-ngx/src/app/core/services/dashboard-utils.service.ts b/ui-ngx/src/app/core/services/dashboard-utils.service.ts index 8355ce2f61..a2a33b73a1 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -348,10 +348,8 @@ export class DashboardUtilsService { private convertDatasourcesFromWidgetType(widgetTypeDescriptor: WidgetTypeDescriptor, config: WidgetConfig, datasources?: Datasource[]): Datasource[] { const newDatasources: Datasource[] = []; - if (datasources) { - datasources.forEach(datasource => { - newDatasources.push(this.convertDatasourceFromWidgetType(widgetTypeDescriptor, config, datasource)); - }); + if (datasources?.length) { + newDatasources.push(this.convertDatasourceFromWidgetType(widgetTypeDescriptor, config, datasources[0])); } return newDatasources; } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss index 6c3b90da84..3856547508 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss @@ -21,9 +21,9 @@ position: relative; } @media #{$mat-gt-xs} { - width: 1200px; + width: 900px; .mat-mdc-dialog-content { - height: 600px; + height: 900px; } } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 39940546c2..cead69ab46 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -1177,6 +1177,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC Widget>(AddWidgetDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + maxWidth: '95vw', data: { dashboard: this.dashboard, aliasController: this.dashboardCtx.aliasController, diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss index a82f9c2f8b..e86f828111 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss @@ -45,6 +45,8 @@ } .preview { + width: 100%; + height: 100%; max-width: 100%; max-height: 100%; object-fit: contain; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html index b5808a8c96..51bb854826 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html @@ -55,7 +55,7 @@
-
+
{{ 'widgets.value-card.icon' | translate }} @@ -87,18 +87,38 @@
-
-
- - {{ 'widgets.value-card.date' | translate }} - -
- - - - - +
+ + {{ 'widgets.value-card.date' | translate }} + +
+ + + + + +
+
+
+
{{ '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/cards/value-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts index 762b26ac42..f00ec8e2e6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts @@ -74,6 +74,16 @@ export class ValueCardBasicConfigComponent extends BasicWidgetConfigComponent { datePreviewFn = this._datePreviewFn.bind(this); + get dateEnabled(): boolean { + const layout: ValueCardLayout = this.valueCardWidgetConfigForm.get('layout').value; + return ![ValueCardLayout.vertical, ValueCardLayout.simplified].includes(layout); + } + + get iconEnabled(): boolean { + const layout: ValueCardLayout = this.valueCardWidgetConfigForm.get('layout').value; + return layout !== ValueCardLayout.simplified; + } + constructor(protected store: Store, protected widgetConfigComponent: WidgetConfigComponent, private cd: ChangeDetectorRef, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html index b603f98a4d..22c4c2aace 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html @@ -22,7 +22,7 @@ {{ 'datakey.latest' | translate }} - + @@ -44,7 +44,7 @@ matTooltipPosition="above">timeline
-
+
@@ -139,7 +139,7 @@ - +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss index 41a003985e..fabd561a97 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss @@ -38,7 +38,16 @@ } .tb-source-field { - width: 140px; + width: 120px; + min-width: 120px; + } + + .tb-key-field { + flex: 1 1 60%; + } + + .tb-label-field { + flex: 1 1 40%; } .tb-color-field, .tb-units-field, .tb-decimals-field { @@ -50,9 +59,11 @@ .tb-units-field { width: 80px; + min-width: 80px; } .tb-color-field, .tb-decimals-field { width: 60px; + min-width: 60px; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html index 03e1b4761b..a2cddcde90 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html @@ -20,8 +20,8 @@
datakey.source
-
datakey.key
-
datakey.label
+
datakey.key
+
datakey.label
datakey.color
widget-config.units-short
widget-config.decimals-short
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss index 6ce13d7adc..7d33fd50a4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss @@ -15,15 +15,25 @@ */ .tb-form-table-header-cell { &.tb-source-header { - width: 140px; + width: 120px; + min-width: 120px; + } + &.tb-key-header { + flex: 1 1 60%; + } + &.tb-label-header { + flex: 1 1 40%; } &.tb-units-header { width: 80px; + min-width: 80px; } &.tb-color-header, &.tb-decimals-header { width: 60px; + min-width: 60px; } &.tb-actions-header { width: 114px; + min-width: 114px; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html index 0b4c774393..e024af20ee 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html @@ -65,7 +65,7 @@ {{key.label}}
:
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss index 7d01f41cb5..415c69ec53 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss @@ -65,9 +65,11 @@ font-weight: normal; font-size: 14px; line-height: 20px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + &.tb-chip-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } .mat-icon.tb-datakey-icon { margin-right: 4px; margin-left: 4px; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts index cc482bfd4e..bf2144cdfd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts @@ -44,6 +44,7 @@ import { widgetSettingsComponentsMap } from '@home/components/widget/lib/setting import { Dashboard } from '@shared/models/dashboard.models'; import { WidgetService } from '@core/http/widget.service'; import { IAliasController } from '@core/api/widget-api.models'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; @Component({ selector: 'tb-widget-settings', @@ -73,6 +74,9 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On @Input() widget: Widget; + @Input() + widgetConfig: WidgetConfigComponentData; + private settingsDirective: string; definedDirectiveError: string; @@ -126,6 +130,11 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On this.definedSettingsComponent.aliasController = this.aliasController; } } + if (propName === 'widgetConfig') { + if (this.definedSettingsComponent) { + this.definedSettingsComponent.widgetConfig = this.widgetConfig; + } + } } } } @@ -214,6 +223,7 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On this.definedSettingsComponent.aliasController = this.aliasController; this.definedSettingsComponent.dashboard = this.dashboard; this.definedSettingsComponent.widget = this.widget; + this.definedSettingsComponent.widgetConfig = this.widgetConfig; this.definedSettingsComponent.functionScopeVariables = this.widgetService.getWidgetScopeVariables(); this.changeSubscription = this.definedSettingsComponent.settingsChanged.subscribe((settings) => { this.updateModel(settings); diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts index 34f2ac464b..0fa061de72 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts @@ -276,6 +276,14 @@ export enum BackgroundType { color = 'color' } +export const backgroundTypeTranslations = new Map( + [ + [BackgroundType.image, 'widgets.background.background-type-image'], + [BackgroundType.imageUrl, 'widgets.background.background-type-image-url'], + [BackgroundType.color, 'widgets.background.background-type-color'] + ] +); + export interface OverlaySettings { enabled: boolean; color: string; @@ -313,11 +321,13 @@ export const backgroundStyle = (background: BackgroundSettings): ComponentStyle }; } else { const imageUrl = background.type === BackgroundType.image ? background.imageBase64 : background.imageUrl; - return { - background: `url(${imageUrl}) no-repeat`, - backgroundSize: 'cover', - backgroundPosition: '50% 50%' - }; + if (imageUrl) { + return { + background: `url(${imageUrl}) no-repeat 50% 50% / cover` + }; + } else { + return {}; + } } }; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html new file mode 100644 index 0000000000..423c727a8d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html @@ -0,0 +1,89 @@ + + +
+
widgets.value-card.value-card-style
+ + + {{ valueCardLayoutTranslationMap.get(layout) | translate }} + + +
+ + {{ 'widgets.value-card.label' | translate }} + +
+ + + + +
+
+
+ + {{ 'widgets.value-card.icon' | translate }} + +
+ + + + + + + + +
+
+
+
widgets.value-card.value
+
+ + + + +
+
+
+ + {{ 'widgets.value-card.date' | translate }} + +
+ + + + + +
+
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts new file mode 100644 index 0000000000..6f76546ac1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts @@ -0,0 +1,200 @@ +/// +/// 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. +/// + +import { Component, Injector } from '@angular/core'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { + valueCardDefaultSettings, + ValueCardLayout, valueCardLayoutImages, + valueCardLayouts, valueCardLayoutTranslations +} from '@home/components/widget/lib/cards/value-card-widget.models'; +import { formatValue, isDefinedAndNotNull } from '@core/utils'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { + DateFormatProcessor, + DateFormatSettings, + getLabel +} from '@home/components/widget/config/widget-settings.models'; + +@Component({ + selector: 'tb-value-card-widget-settings', + templateUrl: './value-card-widget-settings.component.html', + styleUrls: [] +}) +export class ValueCardWidgetSettingsComponent extends WidgetSettingsComponent { + + valueCardLayouts: ValueCardLayout[] = []; + + valueCardLayoutTranslationMap = valueCardLayoutTranslations; + valueCardLayoutImageMap = valueCardLayoutImages; + + horizontal = false; + + valueCardWidgetSettingsForm: UntypedFormGroup; + + valuePreviewFn = this._valuePreviewFn.bind(this); + + datePreviewFn = this._datePreviewFn.bind(this); + + + get label(): string { + return getLabel(this.widgetConfig.config.datasources); + } + + get dateEnabled(): boolean { + const layout: ValueCardLayout = this.valueCardWidgetSettingsForm.get('layout').value; + return ![ValueCardLayout.vertical, ValueCardLayout.simplified].includes(layout); + } + + get iconEnabled(): boolean { + const layout: ValueCardLayout = this.valueCardWidgetSettingsForm.get('layout').value; + return layout !== ValueCardLayout.simplified; + } + + constructor(protected store: Store, + private $injector: Injector, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.valueCardWidgetSettingsForm; + } + + protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) { + const params = widgetConfig.typeParameters as any; + this.horizontal = isDefinedAndNotNull(params.horizontal) ? params.horizontal : false; + this.valueCardLayouts = valueCardLayouts(this.horizontal); + } + + protected defaultSettings(): WidgetSettings { + return valueCardDefaultSettings(this.horizontal); + } + + protected onSettingsSet(settings: WidgetSettings) { + this.valueCardWidgetSettingsForm = this.fb.group({ + layout: [settings.layout, []], + + showLabel: [settings.showLabel, []], + labelFont: [settings.labelFont, []], + labelColor: [settings.labelColor, []], + + showIcon: [settings.showIcon, []], + iconSize: [settings.iconSize, [Validators.min(0)]], + iconSizeUnit: [settings.iconSizeUnit, []], + icon: [settings.icon, []], + iconColor: [settings.iconColor, []], + + valueFont: [settings.valueFont, []], + valueColor: [settings.valueColor, []], + + showDate: [settings.showDate, []], + dateFormat: [settings.dateFormat, []], + dateFont: [settings.dateFont, []], + dateColor: [settings.dateColor, []], + + background: [settings.background, []] + }); + } + + protected validatorTriggers(): string[] { + return ['layout', 'showLabel', 'showIcon', 'showDate']; + } + + protected updateValidators(emitEvent: boolean) { + const layout: ValueCardLayout = this.valueCardWidgetSettingsForm.get('layout').value; + const showLabel: boolean = this.valueCardWidgetSettingsForm.get('showLabel').value; + const showIcon: boolean = this.valueCardWidgetSettingsForm.get('showIcon').value; + const showDate: boolean = this.valueCardWidgetSettingsForm.get('showDate').value; + + const dateEnabled = ![ValueCardLayout.vertical, ValueCardLayout.simplified].includes(layout); + const iconEnabled = layout !== ValueCardLayout.simplified; + + if (showLabel) { + this.valueCardWidgetSettingsForm.get('labelFont').enable(); + this.valueCardWidgetSettingsForm.get('labelColor').enable(); + } else { + this.valueCardWidgetSettingsForm.get('labelFont').disable(); + this.valueCardWidgetSettingsForm.get('labelColor').disable(); + } + + if (iconEnabled) { + this.valueCardWidgetSettingsForm.get('showIcon').enable({emitEvent: false}); + if (showIcon) { + this.valueCardWidgetSettingsForm.get('iconSize').enable(); + this.valueCardWidgetSettingsForm.get('iconSizeUnit').enable(); + this.valueCardWidgetSettingsForm.get('icon').enable(); + this.valueCardWidgetSettingsForm.get('iconColor').enable(); + } else { + this.valueCardWidgetSettingsForm.get('iconSize').disable(); + this.valueCardWidgetSettingsForm.get('iconSizeUnit').disable(); + this.valueCardWidgetSettingsForm.get('icon').disable(); + this.valueCardWidgetSettingsForm.get('iconColor').disable(); + } + } else { + this.valueCardWidgetSettingsForm.get('showIcon').disable({emitEvent: false}); + this.valueCardWidgetSettingsForm.get('iconSize').disable(); + this.valueCardWidgetSettingsForm.get('iconSizeUnit').disable(); + this.valueCardWidgetSettingsForm.get('icon').disable(); + this.valueCardWidgetSettingsForm.get('iconColor').disable(); + } + + if (dateEnabled) { + this.valueCardWidgetSettingsForm.get('showDate').enable({emitEvent: false}); + if (showDate) { + this.valueCardWidgetSettingsForm.get('dateFormat').enable(); + this.valueCardWidgetSettingsForm.get('dateFont').enable(); + this.valueCardWidgetSettingsForm.get('dateColor').enable(); + } else { + this.valueCardWidgetSettingsForm.get('dateFormat').disable(); + this.valueCardWidgetSettingsForm.get('dateFont').disable(); + this.valueCardWidgetSettingsForm.get('dateColor').disable(); + } + } else { + this.valueCardWidgetSettingsForm.get('showDate').disable({emitEvent: false}); + this.valueCardWidgetSettingsForm.get('dateFormat').disable(); + this.valueCardWidgetSettingsForm.get('dateFont').disable(); + this.valueCardWidgetSettingsForm.get('dateColor').disable(); + } + this.valueCardWidgetSettingsForm.get('showIcon').updateValueAndValidity({emitEvent: false}); + this.valueCardWidgetSettingsForm.get('showDate').updateValueAndValidity({emitEvent: false}); + this.valueCardWidgetSettingsForm.get('labelFont').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('labelColor').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('iconSize').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('iconSizeUnit').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('icon').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('iconColor').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('dateFormat').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('dateFont').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('dateColor').updateValueAndValidity({emitEvent}); + } + + private _valuePreviewFn(): string { + const units: string = this.widgetConfig.config.units; + const decimals: number = this.widgetConfig.config.decimals; + return formatValue(22, decimals, units, true); + } + + private _datePreviewFn(): string { + const dateFormat: DateFormatSettings = this.valueCardWidgetSettingsForm.get('dateFormat').value; + const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat); + processor.update(Date.now()); + return processor.formatted; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html new file mode 100644 index 0000000000..ca5d4bc8b9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html @@ -0,0 +1,87 @@ + +
+
widgets.background.background-settings
+
+
+
widgets.background.background
+ + + {{ backgroundTypeTranslationsMap.get(type) | translate }} + + +
+ +
+
widgets.background.image-url
+ + + +
+
+
widgets.color.color
+ + +
+
+
+
widgets.background.overlay
+ + {{ 'widgets.background.enable-overlay' | translate }} + +
+
widgets.color.color
+ + +
+
+
widgets.background.blur
+ + +
px
+
+
+
+
+
+ widgets.background.preview +
+
+
+
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss new file mode 100644 index 0000000000..258117512a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss @@ -0,0 +1,73 @@ +/** + * 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. + */ + +@import '../../../../../../../../scss/constants'; + +.tb-background-settings-panel { + width: 620px; + display: flex; + flex-direction: column; + gap: 16px; + @media #{$mat-lt-md} { + width: 90vw; + } + .tb-background-settings-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-background-settings-preview { + flex: 1; + background: rgba(0, 0, 0, 0.04); + display: flex; + flex-direction: column; + padding: 12px 16px 24px 16px; + align-items: center; + gap: 12px; + } + .tb-background-settings-preview-title { + align-self: stretch; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 24px; + color: rgba(0, 0, 0, 0.38); + } + .tb-background-settings-preview-box { + position: relative; + width: 136px; + height: 118px; + border-radius: 2.666px; + } + .tb-background-settings-preview-overlay { + position: absolute; + border-radius: 2.666px; + top: 7.998px; + bottom: 7.998px; + left: 7.998px; + right: 7.998px; + } + .tb-background-settings-panel-buttons { + height: 40px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts new file mode 100644 index 0000000000..51d2ddec1b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts @@ -0,0 +1,120 @@ +/// +/// 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. +/// + +import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { + backgroundStyle, + overlayStyle, + BackgroundSettings, + BackgroundType, + backgroundTypeTranslations, ComponentStyle +} from '@home/components/widget/config/widget-settings.models'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; + +@Component({ + selector: 'tb-background-settings-panel', + templateUrl: './background-settings-panel.component.html', + providers: [], + styleUrls: ['./background-settings-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class BackgroundSettingsPanelComponent extends PageComponent implements OnInit { + + @Input() + backgroundSettings: BackgroundSettings; + + @Input() + popover: TbPopoverComponent; + + @Output() + backgroundSettingsApplied = new EventEmitter(); + + backgroundType = BackgroundType; + + backgroundTypes = Object.keys(BackgroundType) as BackgroundType[]; + + backgroundTypeTranslationsMap = backgroundTypeTranslations; + + backgroundSettingsFormGroup: UntypedFormGroup; + + backgroundStyle: ComponentStyle = {}; + overlayStyle: ComponentStyle = {}; + + constructor(private fb: UntypedFormBuilder, + protected store: Store) { + super(store); + } + + ngOnInit(): void { + this.backgroundSettingsFormGroup = this.fb.group( + { + type: [this.backgroundSettings?.type, []], + imageBase64: [this.backgroundSettings?.imageBase64, []], + imageUrl: [this.backgroundSettings?.imageUrl, []], + color: [this.backgroundSettings?.color, []], + overlay: this.fb.group({ + enabled: [this.backgroundSettings?.overlay?.enabled, []], + color: [this.backgroundSettings?.overlay?.color, []], + blur: [this.backgroundSettings?.overlay?.blur, []] + }) + } + ); + this.backgroundSettingsFormGroup.get('type').valueChanges.subscribe(() => { + setTimeout(() => {this.popover?.updatePosition();}, 0); + }); + this.backgroundSettingsFormGroup.get('overlay').get('enabled').valueChanges.subscribe(() => { + this.updateValidators(); + }); + this.backgroundSettingsFormGroup.valueChanges.subscribe(() => { + this.updateBackgroundStyle(); + }); + this.updateValidators(); + this.updateBackgroundStyle(); + } + + cancel() { + this.popover?.hide(); + } + + applyColorSettings() { + const backgroundSettings = this.backgroundSettingsFormGroup.value; + this.backgroundSettingsApplied.emit(backgroundSettings); + } + + private updateValidators() { + const overlayEnabled: boolean = this.backgroundSettingsFormGroup.get('overlay').get('enabled').value; + if (overlayEnabled) { + this.backgroundSettingsFormGroup.get('overlay').get('color').enable(); + this.backgroundSettingsFormGroup.get('overlay').get('blur').enable(); + } else { + this.backgroundSettingsFormGroup.get('overlay').get('color').disable(); + this.backgroundSettingsFormGroup.get('overlay').get('blur').disable(); + } + this.backgroundSettingsFormGroup.get('overlay').get('color').updateValueAndValidity({emitEvent: false}); + this.backgroundSettingsFormGroup.get('overlay').get('blur').updateValueAndValidity({emitEvent: false}); + } + + private updateBackgroundStyle() { + const background: BackgroundSettings = this.backgroundSettingsFormGroup.value; + this.backgroundStyle = backgroundStyle(background); + this.overlayStyle = overlayStyle(background.overlay); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html new file mode 100644 index 0000000000..e9e1b99b0e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html @@ -0,0 +1,30 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss new file mode 100644 index 0000000000..6f73fbffa4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss @@ -0,0 +1,41 @@ +/** + * 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. + */ +button.mat-mdc-button-base.tb-box-button.tb-background-settings { + padding: 0; + .mat-mdc-button-persistent-ripple { + z-index: 2; + } + .tb-color-preview { + width: 38px; + min-width: 38px; + height: 38px; + &.box { + .tb-color-result { + &:after { + border: none; + } + } + .tb-color-overlay { + position: absolute; + border-radius: 3px; + top: 4px; + bottom: 4px; + left: 4px; + right: 4px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts new file mode 100644 index 0000000000..f8162575a3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts @@ -0,0 +1,120 @@ +/// +/// 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. +/// + +import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef, ViewEncapsulation } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { + BackgroundSettings, + backgroundStyle, + BackgroundType, + ComponentStyle, + overlayStyle +} from '@home/components/widget/config/widget-settings.models'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { + BackgroundSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/background-settings-panel.component'; + +@Component({ + selector: 'tb-background-settings', + templateUrl: './background-settings.component.html', + styleUrls: ['./background-settings.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => BackgroundSettingsComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class BackgroundSettingsComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + backgroundType = BackgroundType; + + modelValue: BackgroundSettings; + + backgroundStyle: ComponentStyle = {}; + + overlayStyle: ComponentStyle = {}; + + private propagateChange = null; + + constructor(private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef) {} + + ngOnInit(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + this.updateBackgroundStyle(); + } + + writeValue(value: BackgroundSettings): void { + this.modelValue = value; + this.updateBackgroundStyle(); + } + + openBackgroundSettingsPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + backgroundSettings: this.modelValue + }; + const backgroundSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, BackgroundSettingsPanelComponent, 'left', true, null, + ctx, + {}, + {}, {}, true); + backgroundSettingsPanelPopover.tbComponentRef.instance.popover = backgroundSettingsPanelPopover; + backgroundSettingsPanelPopover.tbComponentRef.instance.backgroundSettingsApplied.subscribe((backgroundSettings) => { + backgroundSettingsPanelPopover.hide(); + this.modelValue = backgroundSettings; + this.updateBackgroundStyle(); + this.propagateChange(this.modelValue); + }); + } + } + + private updateBackgroundStyle() { + if (!this.disabled) { + this.backgroundStyle = backgroundStyle(this.modelValue); + this.overlayStyle = overlayStyle(this.modelValue.overlay); + } else { + this.backgroundStyle = {}; + this.overlayStyle = {}; + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts index e538653703..d75c4a5fc9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts @@ -21,14 +21,14 @@ import { Directive, ElementRef, forwardRef, - Input, + Input, OnChanges, OnDestroy, OnInit, - QueryList, + QueryList, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms'; import { coerceBoolean } from '@shared/decorators/coercion'; -import { Observable, Subject } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; import { map, share, startWith, takeUntil } from 'rxjs/operators'; import { BreakpointObserver } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; @@ -73,7 +73,7 @@ export class ImageCardsSelectOptionDirective { ], encapsulation: ViewEncapsulation.None }) -export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit, AfterContentInit, OnDestroy { +export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit, OnChanges, AfterContentInit, OnDestroy { @ContentChildren(ImageCardsSelectOptionDirective) imageCardsSelectOptions: QueryList; @@ -107,20 +107,33 @@ export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit, private _destroyed = new Subject(); + private _colsChanged = new BehaviorSubject(null); + constructor(private breakpointObserver: BreakpointObserver) { this.valueFormControl = new UntypedFormControl(''); } ngOnInit(): void { const gridColumns = this.breakpointObserver.isMatched(MediaBreakpoints['lt-md']) ? this.colsLtMd : this.cols; - this.cols$ = this.breakpointObserver - .observe(MediaBreakpoints['lt-md']).pipe( - map((state) => state.matches ? this.colsLtMd : this.cols), + this.cols$ = combineLatest({state: this.breakpointObserver + .observe(MediaBreakpoints['lt-md']), colsChanged: this._colsChanged.asObservable()}).pipe( + map((data) => data.state.matches ? this.colsLtMd : this.cols), startWith(gridColumns), share() ); } + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (['cols', 'colsLtMd'].includes(propName)) { + this._colsChanged.next(null); + } + } + } + } + ngAfterContentInit(): void { this.imageCardsSelectOptions.changes.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() => { this.syncImageCardsSelectOptions(); 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 68b84578c1..748d90649d 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 @@ -281,6 +281,13 @@ import { DateFormatSelectComponent } from '@home/components/widget/lib/settings/ import { DateFormatSettingsPanelComponent } from '@home/components/widget/lib/settings/common/date-format-settings-panel.component'; +import { BackgroundSettingsComponent } from '@home/components/widget/lib/settings/common/background-settings.component'; +import { + BackgroundSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/background-settings-panel.component'; +import { + ValueCardWidgetSettingsComponent +} from '@home/components/widget/lib/settings/cards/value-card-widget-settings.component'; @NgModule({ declarations: [ @@ -391,7 +398,10 @@ import { ColorSettingsPanelComponent, CssUnitSelectComponent, DateFormatSelectComponent, - DateFormatSettingsPanelComponent + DateFormatSettingsPanelComponent, + BackgroundSettingsComponent, + BackgroundSettingsPanelComponent, + ValueCardWidgetSettingsComponent ], imports: [ CommonModule, @@ -506,7 +516,10 @@ import { ColorSettingsPanelComponent, CssUnitSelectComponent, DateFormatSelectComponent, - DateFormatSettingsPanelComponent + DateFormatSettingsPanelComponent, + BackgroundSettingsComponent, + BackgroundSettingsPanelComponent, + ValueCardWidgetSettingsComponent ] }) export class WidgetSettingsModule { @@ -575,5 +588,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html index 6601aa96db..43b54884ec 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html @@ -19,7 +19,6 @@ [fullscreenBackgroundStyle]="dashboardStyle" [fullscreenBackgroundImage]="backgroundImage" (fullscreenChanged)="onFullscreenChanged($event)" - fxLayout="column" class="tb-widget" [ngClass]="{ 'tb-highlighted': isHighlighted(widget), @@ -32,8 +31,11 @@ (mousedown)="onMouseDown($event)" (click)="onClicked($event)" (contextmenu)="onContextMenu($event)"> -
-
+
+
-
- + diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss index a364189372..52caeb2a5c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss @@ -14,9 +14,14 @@ * limitations under the License. */ -tb-widget.tb-widget { - position: relative; - height: 100%; +.tb-widget-container { + position: absolute; + inset: 0; +} + +.tb-widget { + position: absolute; + inset: 0; margin: 0; overflow: hidden; outline: none; @@ -25,15 +30,27 @@ tb-widget.tb-widget { } div.tb-widget { - position: relative; - height: 100%; - margin: 0; - overflow: hidden; - outline: none; - - transition: all .2s ease-in-out; + display: flex; + flex-direction: column; + .tb-widget-header { + display: flex; + flex-direction: row; + place-content: flex-start space-between; + align-items: flex-start; + &-absolute { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 1; + } + } .tb-widget-title { + display: flex; + flex-direction: column; + place-content: flex-start center; + align-items: flex-start; max-height: 65px; padding-top: 5px; padding-left: 5px; @@ -63,6 +80,10 @@ div.tb-widget { } .tb-widget-actions { + display: flex; + flex-direction: row; + place-content: center flex-start; + align-items: center; z-index: 19; margin: 5px 0 0; @@ -104,13 +125,11 @@ div.tb-widget { } .tb-widget-content { + flex: 1; + position: relative; &.tb-no-interaction { pointer-events: none; } - tb-widget { - position: relative; - width: 100%; - } } &.tb-highlighted { 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 7e9b9cb6e7..c276999d83 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 @@ -409,6 +409,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI elem.classList.add(this.widgetContext.widgetNamespace); this.widgetType = this.widgetInfo.widgetTypeFunction; this.typeParameters = this.widgetInfo.typeParameters; + this.widgetContext.absoluteHeader = this.typeParameters.absoluteHeader; if (!this.widgetType) { this.widgetTypeInstance = {}; 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 b16e880e02..18c8ccd19e 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 @@ -265,6 +265,8 @@ export class WidgetContext { hiddenData?: Array<{data: DataSet}>; timeWindow?: WidgetTimewindow; + absoluteHeader?: boolean; + hideTitlePanel = false; widgetTitle?: string; diff --git a/ui-ngx/src/app/shared/components/unit-input.component.html b/ui-ngx/src/app/shared/components/unit-input.component.html index d001a43ef2..0ae14b8ba9 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.html +++ b/ui-ngx/src/app/shared/components/unit-input.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + > { if (this.fetchUnits$ === null) { - this.fetchUnits$ = this.resourcesService.loadJsonResource>(unitsModels).pipe( + this.fetchUnits$ = getUnits(this.resourcesService).pipe( map(units => units.map(u => ({ symbol: u.symbol, name: this.translate.instant(u.name), diff --git a/ui-ngx/src/app/shared/models/unit.models.ts b/ui-ngx/src/app/shared/models/unit.models.ts index 797e8a0c4a..7d9f88a068 100644 --- a/ui-ngx/src/app/shared/models/unit.models.ts +++ b/ui-ngx/src/app/shared/models/unit.models.ts @@ -14,6 +14,9 @@ /// limitations under the License. /// +import { ResourcesService } from '@core/services/resources.service'; +import { Observable } from 'rxjs'; + export interface Unit { name: string; symbol: string; @@ -30,3 +33,6 @@ export const searchUnits = (_units: Array, searchText: string): Array> => + resourcesService.loadJsonResource('/assets/metadata/units.json'); diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 716a4cf8b4..e0d9918540 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -19,7 +19,6 @@ import { TenantId } from '@shared/models/id/tenant-id'; import { WidgetTypeId } from '@shared/models/id/widget-type-id'; import { AggregationType, ComparisonDuration, Timewindow } from '@shared/models/time/time.models'; import { EntityType } from '@shared/models/entity-type.models'; -import { AlarmSearchStatus, AlarmSeverity } from '@shared/models/alarm.models'; import { DataKeyType } from './telemetry/telemetry.models'; import { EntityId } from '@shared/models/id/entity-id'; import * as moment_ from 'moment'; @@ -40,6 +39,7 @@ import { Observable } from 'rxjs'; import { Dashboard } from '@shared/models/dashboard.models'; import { IAliasController } from '@core/api/widget-api.models'; import { isEmptyStr } from '@core/utils'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; export enum widgetType { timeseries = 'timeseries', @@ -182,6 +182,7 @@ export interface WidgetTypeParameters { processNoDataByWidget?: boolean; previewWidth?: string; previewHeight?: string; + absoluteHeader?: boolean; } export interface WidgetControllerDescriptor { @@ -706,6 +707,7 @@ export interface IWidgetSettingsComponent { aliasController: IAliasController; dashboard: Dashboard; widget: Widget; + widgetConfig: WidgetConfigComponentData; functionScopeVariables: string[]; settings: WidgetSettings; settingsChanged: Observable; @@ -737,6 +739,17 @@ export abstract class WidgetSettingsComponent extends PageComponent implements widget: Widget; + widgetConfigValue: WidgetConfigComponentData; + + set widgetConfig(value: WidgetConfigComponentData) { + this.widgetConfigValue = value; + this.onWidgetConfigSet(value); + } + + get widgetConfig(): WidgetConfigComponentData { + return this.widgetConfigValue; + } + functionScopeVariables: string[]; settingsValue: WidgetSettings; @@ -848,4 +861,7 @@ export abstract class WidgetSettingsComponent extends PageComponent implements return {}; } + protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) { + } + } 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 0f96a5f1dc..536c2dcc4d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4690,6 +4690,7 @@ "advanced-widget-style": "Advanced widget style", "card-buttons": "Card buttons", "show-card-buttons": "Show card buttons", + "card-border-radius": "Card border radius", "card-appearance": "Card appearance", "color": "Color" }, @@ -4702,6 +4703,18 @@ "invalid-widget-type-file-error": "Unable to import widget type: Invalid widget type data structure." }, "widgets": { + "background": { + "background": "Background", + "background-settings": "Background settings", + "background-type-image": "Upload image", + "background-type-image-url": "Image URL", + "background-type-color": "Solid color", + "image-url": "Image URL", + "overlay": "Overlay", + "enable-overlay": "Enable overlay", + "blur": "Blur", + "preview": "Preview" + }, "chart": { "common-settings": "Common settings", "enable-stacking-mode": "Enable stacking mode", @@ -5665,7 +5678,8 @@ "label": "Label", "icon": "Icon", "value": "Value", - "date": "Date" + "date": "Date", + "value-card-style": "Value card style" }, "table": { "common-table-settings": "Common Table Settings", diff --git a/ui-ngx/src/assets/model/units.json b/ui-ngx/src/assets/metadata/units.json similarity index 100% rename from ui-ngx/src/assets/model/units.json rename to ui-ngx/src/assets/metadata/units.json diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index d8bbdf743d..75fc0845cf 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -306,10 +306,7 @@ pre.tb-highlight { .tb-fullscreen { position: fixed !important; - top: 0; - left: 0; - width: 100% !important; - height: 100% !important; + inset: 0 !important; } .tb-fullscreen-parent { @@ -983,10 +980,7 @@ mat-label { min-width: 100%; max-width: none !important; position: absolute !important; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; .mat-mdc-dialog-container { > *:first-child, form { min-width: 100% !important; @@ -1004,10 +998,7 @@ mat-label { min-width: 100%; max-width: none !important; position: absolute !important; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; .mat-mdc-dialog-container { > *:first-child, form { min-width: 100% !important; @@ -1022,10 +1013,7 @@ mat-label { .tb-absolute-fill { position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; } .tb-layout-fill { @@ -1037,10 +1025,7 @@ mat-label { .tb-progress-cover { position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; + inset: 0; z-index: 6; background-color: #eee; opacity: 1; From dc3f3ceafbfbf9cc06d402c1a8e0bc5c16b77094 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 27 Jul 2023 17:23:53 +0300 Subject: [PATCH 131/168] UI: Add color picker input for multiple input widget --- .../widget/lib/multiple-input-widget.component.html | 11 +++++++++++ .../widget/lib/multiple-input-widget.component.ts | 2 +- ...te-multiple-attributes-key-settings.component.html | 3 +++ ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 0a757fd34f..c0886046c0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -172,6 +172,17 @@
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index 534be7c676..05a972e296 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -54,7 +54,7 @@ type FieldAlignment = 'row' | 'column'; type MultipleInputWidgetDataKeyType = 'server' | 'shared' | 'timeseries'; export type MultipleInputWidgetDataKeyValueType = 'string' | 'double' | 'integer' | 'JSON' | 'booleanCheckbox' | 'booleanSwitch' | - 'dateTime' | 'date' | 'time' | 'select'; + 'dateTime' | 'date' | 'time' | 'select' | 'colorPicker'; type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly'; type ConvertGetValueFunction = (value: any, ctx: WidgetContext) => any; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html index 3c62810000..22eb191ec3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html @@ -69,6 +69,9 @@ {{ 'widgets.input-widgets.datakey-value-type-json' | translate }} + + {{ 'widgets.input-widgets.datakey-value-type-color-picker' | translate }} + 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 c5ec1fca40..fbfb96cac0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4792,6 +4792,7 @@ "datakey-value-type-date": "Date", "datakey-value-type-time": "Time", "datakey-value-type-select": "Select", + "datakey-value-type-color-picker": "Color Picker", "value-is-required": "Value is required", "ability-to-edit-attribute": "Ability to edit attribute", "ability-to-edit-attribute-editable": "Editable (default)", From 80fbc89e20b8a78b79cc150d9df436c89855423e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 27 Jul 2023 17:39:04 +0300 Subject: [PATCH 132/168] UI: use .mat-icon class selector instead of mat-icon tag for tb-icon component compatibility. --- .../components/attribute/attribute-table.component.scss | 2 +- .../home/components/widget/config/data-keys.component.scss | 2 +- .../widget/lib/edges-overview-widget.component.scss | 4 ++-- .../widget/lib/entities-hierarchy-widget.component.scss | 4 ++-- .../widget/lib/navigation-card-widget.component.scss | 2 +- .../widget/lib/trip-animation/trip-animation.component.scss | 2 +- ui-ngx/src/app/modules/home/menu/side-menu.component.scss | 2 +- .../home/pages/rulechain/rulechain-page.component.scss | 4 ++-- .../modules/home/pages/rulechain/rulenode.component.scss | 2 +- .../modules/home/pages/widget/widget-editor.component.scss | 2 +- ui-ngx/src/app/shared/components/fab-toolbar.component.scss | 6 +++--- .../time/history-selector/history-selector.component.scss | 4 ++-- ui-ngx/src/app/shared/components/user-menu.component.scss | 2 +- ui-ngx/src/theme.scss | 2 +- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss index 831762d1e4..b33dfdbb20 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss @@ -104,7 +104,7 @@ } mat-cell.tb-value-cell { cursor: pointer; - mat-icon { + .mat-icon { height: 24px; width: 24px; font-size: 24px; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss index 415c69ec53..1664dafb7f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss @@ -49,7 +49,7 @@ padding: 3px; height: 24px; cursor: move; - mat-icon { + .mat-icon { pointer-events: none; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.scss index f5844d8aac..9b2d35e030 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.scss @@ -71,7 +71,7 @@ background-size: 18px 18px; } - mat-icon.node-icon { + .mat-icon.node-icon { width: 22px; min-width: 22px; height: 22px; @@ -109,7 +109,7 @@ background-size: 24px 24px; } - mat-icon.node-icon { + .mat-icon.node-icon { width: 40px; min-width: 40px; height: 40px; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss index 6731690b0b..426d81b723 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss @@ -64,7 +64,7 @@ background-size: 18px 18px; } - mat-icon.node-icon { + .mat-icon.node-icon { width: 22px; min-width: 22px; height: 22px; @@ -102,7 +102,7 @@ background-size: 24px 24px; } - mat-icon.node-icon { + .mat-icon.node-icon { width: 40px; min-width: 40px; height: 40px; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss index a04f82dce7..b9c3e034a7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss @@ -31,7 +31,7 @@ display: flex; flex-direction: column; align-items: center; - mat-icon { + .mat-icon { margin: auto !important; } span.mdc-button__label { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/trip-animation/trip-animation.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/trip-animation/trip-animation.component.scss index d379c9ff8a..4118a26800 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/trip-animation/trip-animation.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/trip-animation/trip-animation.component.scss @@ -54,7 +54,7 @@ line-height: 24px; z-index: 999; - mat-icon { + .mat-icon { width: 24px; height: 24px; diff --git a/ui-ngx/src/app/modules/home/menu/side-menu.component.scss b/ui-ngx/src/app/modules/home/menu/side-menu.component.scss index fc9df865a1..dbba5e78a9 100644 --- a/ui-ngx/src/app/modules/home/menu/side-menu.component.scss +++ b/ui-ngx/src/app/modules/home/menu/side-menu.component.scss @@ -49,7 +49,7 @@ &.tb-active { background-color: rgba(255, 255, 255, .15); } - mat-icon { + .mat-icon { margin-right: 8px; margin-left: 0; min-width: 1.125rem; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss index b109b4753d..db8d6322f3 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss @@ -117,7 +117,7 @@ min-height: 32px; padding: 6px; line-height: 20px; - mat-icon { + .mat-icon { width: 20px; min-width: 20px; height: 20px; @@ -216,7 +216,7 @@ cursor: pointer; box-sizing: border-box; - mat-icon{ + .mat-icon{ width: 16px; min-width: 16px; height: 16px; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.scss index 38ef76feaa..0811288423 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.scss +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.scss @@ -86,7 +86,7 @@ background-color: #a3eaa9; } - mat-icon, img { + .mat-icon, img { margin: auto; width: 20px; min-width: 20px; diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss index b8359b38db..f928dde955 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss @@ -185,7 +185,7 @@ mat-toolbar.tb-edit-toolbar { white-space: nowrap; height: 28px; - mat-icon { + .mat-icon { height: 20px; width: 20px; font-size: 20px; diff --git a/ui-ngx/src/app/shared/components/fab-toolbar.component.scss b/ui-ngx/src/app/shared/components/fab-toolbar.component.scss index e8e0c0b9f2..42f2e0c9eb 100644 --- a/ui-ngx/src/app/shared/components/fab-toolbar.component.scss +++ b/ui-ngx/src/app/shared/components/fab-toolbar.component.scss @@ -74,7 +74,7 @@ mat-fab-toolbar { button.mat-mdc-fab { overflow: visible !important; opacity: .5; - mat-icon { + .mat-icon { position: relative; z-index: $z-index-fab + 2; opacity: 1; @@ -146,7 +146,7 @@ mat-fab-toolbar { box-shadow: none; opacity: 1; - mat-icon { + .mat-icon { opacity: 0; } } @@ -163,7 +163,7 @@ mat-fab-toolbar { mat-fab-trigger { button.mat-mdc-fab { transition: opacity .3s cubic-bezier(.55, 0, .55, .2) .2s; - mat-icon { + .mat-icon { transition: all $icon-delay ease-in; } } diff --git a/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.scss b/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.scss index 6f38e6a6a5..f6f24e2608 100644 --- a/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.scss +++ b/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.scss @@ -51,7 +51,7 @@ margin: 2px; line-height: 24px; - mat-icon { + .mat-icon { width: 24px; height: 24px; @@ -93,7 +93,7 @@ margin: 0; line-height: 28px; - mat-icon { + .mat-icon { width: 24px; height: 24px; font-size: 24px; diff --git a/ui-ngx/src/app/shared/components/user-menu.component.scss b/ui-ngx/src/app/shared/components/user-menu.component.scss index b0d3acbf51..c435fe2867 100644 --- a/ui-ngx/src/app/shared/components/user-menu.component.scss +++ b/ui-ngx/src/app/shared/components/user-menu.component.scss @@ -36,7 +36,7 @@ } - mat-icon.tb-mini-avatar { + .mat-icon.tb-mini-avatar { width: 36px; height: 36px; margin: auto 8px; diff --git a/ui-ngx/src/theme.scss b/ui-ngx/src/theme.scss index 9aa3e61d39..df1b2bca3e 100644 --- a/ui-ngx/src/theme.scss +++ b/ui-ngx/src/theme.scss @@ -212,7 +212,7 @@ $tb-dark-theme: map_merge($tb-dark-theme, $color); &.mat-primary { @include _mat-toolbar-inverse-color($primary); button.mat-mdc-icon-button { - mat-icon { + .mat-icon { color: mat.get-color-from-palette($primary); } } From f9af643f6cb6e94842dbda04bda3da87afddf426 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 28 Jul 2023 10:35:02 +0300 Subject: [PATCH 133/168] UI: Refactoring --- .../lib/multiple-input-widget.component.html | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index c0886046c0..9d5bf1420c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -172,17 +172,16 @@
-
- - -
+ +
From a659d1b7e6c8614923e4d9b1e42df524165dabab Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 28 Jul 2023 11:02:27 +0300 Subject: [PATCH 134/168] UI: Change value type for color --- .../widget/lib/multiple-input-widget.component.html | 2 +- .../components/widget/lib/multiple-input-widget.component.ts | 2 +- .../update-multiple-attributes-key-settings.component.html | 4 ++-- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 9d5bf1420c..fa51899c4a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -173,7 +173,7 @@
any; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html index 22eb191ec3..d69a4b0713 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html @@ -69,8 +69,8 @@ {{ 'widgets.input-widgets.datakey-value-type-json' | translate }} - - {{ 'widgets.input-widgets.datakey-value-type-color-picker' | translate }} + + {{ 'widgets.input-widgets.datakey-value-type-color' | translate }} 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 fbfb96cac0..af441665bd 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4792,7 +4792,7 @@ "datakey-value-type-date": "Date", "datakey-value-type-time": "Time", "datakey-value-type-select": "Select", - "datakey-value-type-color-picker": "Color Picker", + "datakey-value-type-color": "Color", "value-is-required": "Value is required", "ability-to-edit-attribute": "Ability to edit attribute", "ability-to-edit-attribute-editable": "Editable (default)", From 3f18c2e43636633766bdc0fdb4dce787a06e66bd Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 28 Jul 2023 11:57:32 +0300 Subject: [PATCH 135/168] UI: update label --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e0c07480cf..9ce52d14ab 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -938,7 +938,7 @@ "selected-customers": "{ count, plural, =1 {1 customer} other {# customers} } selected", "edges": "Customer edge instances", "manage-edges": "Manage edges", - "assign-customer": "Assign customer" + "assign-customer": "Assign to customer" }, "datetime": { "date-from": "Date from", From aec44cf72c335cff6eb2adbacca93b38915174a0 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 28 Jul 2023 12:06:30 +0300 Subject: [PATCH 136/168] UI: Refactoring --- .../home/components/wizard/device-wizard-dialog.component.html | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 08d71e1229..fd427923f7 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -76,7 +76,7 @@
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 9ce52d14ab..42fb38ed54 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -937,8 +937,7 @@ "search": "Search customers", "selected-customers": "{ count, plural, =1 {1 customer} other {# customers} } selected", "edges": "Customer edge instances", - "manage-edges": "Manage edges", - "assign-customer": "Assign to customer" + "manage-edges": "Manage edges" }, "datetime": { "date-from": "Date from", From d9c39c362eba7c579061b1a7a75248d2effaf3e4 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 28 Jul 2023 14:20:33 +0200 Subject: [PATCH 137/168] refactored due to comments --- .../src/main/resources/thingsboard.yml | 2 +- .../queue/discovery/ZkDiscoveryService.java | 26 +++++--- .../discovery/ZkDiscoveryServiceTest.java | 62 ++++++++++++------- .../src/main/resources/tb-vc-executor.yml | 2 +- .../src/main/resources/tb-coap-transport.yml | 2 +- .../src/main/resources/tb-http-transport.yml | 2 +- .../src/main/resources/tb-lwm2m-transport.yml | 2 +- .../src/main/resources/tb-mqtt-transport.yml | 2 +- .../src/main/resources/tb-snmp-transport.yml | 2 +- 9 files changed, 62 insertions(+), 40 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 9cec475335..b6fd99dcec 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -96,7 +96,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cluster: stats: diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 50378d3387..44999d016a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.queue.discovery; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.ProtocolStringList; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.curator.framework.CuratorFramework; @@ -68,7 +69,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private Integer zkSessionTimeout; @Value("${zk.zk_dir}") private String zkDir; - @Value("${zk.recalculate_delay:120000}") + @Value("${zk.recalculate_delay:60000}") private Long recalculateDelay; protected final ConcurrentHashMap> delayedTasks; @@ -294,35 +295,39 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi log.error("Failed to decode server instance for node {}", data.getPath(), e); throw e; } - log.debug("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), instance.getServiceId()); + + String serviceId = instance.getServiceId(); + ProtocolStringList serviceTypesList = instance.getServiceTypesList(); + + log.trace("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), serviceId); switch (pathChildrenCacheEvent.getType()) { case CHILD_ADDED: - ScheduledFuture task = delayedTasks.remove(instance.getServiceId()); + ScheduledFuture task = delayedTasks.remove(serviceId); if (task != null) { if (task.cancel(false)) { log.debug("[{}] Recalculate partitions ignored. Service was restarted in time [{}].", - instance.getServiceId(), instance.getServiceTypesList()); + serviceId, serviceTypesList); } else { log.debug("[{}] Going to recalculate partitions. Service was not restarted in time [{}]!", - instance.getServiceId(), instance.getServiceTypesList()); + serviceId, serviceTypesList); recalculatePartitions(); } } else { - log.debug("[{}] Going to recalculate partitions due to adding new node [{}].", - instance.getServiceId(), instance.getServiceTypesList()); + log.trace("[{}] Going to recalculate partitions due to adding new node [{}].", + serviceId, serviceTypesList); recalculatePartitions(); } break; case CHILD_REMOVED: ScheduledFuture future = zkExecutorService.schedule(() -> { log.debug("[{}] Going to recalculate partitions due to removed node [{}]", - instance.getServiceId(), instance.getServiceTypesList()); - ScheduledFuture removedTask = delayedTasks.remove(instance.getServiceId()); + serviceId, serviceTypesList); + ScheduledFuture removedTask = delayedTasks.remove(serviceId); if (removedTask != null) { recalculatePartitions(); } }, recalculateDelay, TimeUnit.MILLISECONDS); - delayedTasks.put(instance.getServiceId(), future); + delayedTasks.put(serviceId, future); break; default: break; @@ -334,6 +339,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi * Synchronized to ensure that other servers info is up to date * */ synchronized void recalculatePartitions() { + delayedTasks.values().forEach(future -> future.cancel(false)); delayedTasks.clear(); partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); } diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java index 38cad217aa..a8810efd0e 100644 --- a/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java +++ b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java @@ -63,68 +63,76 @@ public class ZkDiscoveryServiceTest { @Mock private PathChildrenCache cache; - private ScheduledExecutorService zkExecutorService; - @Mock private CuratorFramework curatorFramework; private ZkDiscoveryService zkDiscoveryService; + private static final long RECALCULATE_DELAY = 100L; + + final TransportProtos.ServiceInfo currentInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("tb-rule-engine-0").build(); + final ChildData currentData = new ChildData("/thingsboard/nodes/0000000010", null, currentInfo.toByteArray()); + final TransportProtos.ServiceInfo childInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("tb-rule-engine-1").build(); + final ChildData childData = new ChildData("/thingsboard/nodes/0000000020", null, childInfo.toByteArray()); + @Before public void setup() { zkDiscoveryService = Mockito.spy(new ZkDiscoveryService(serviceInfoProvider, partitionService)); - zkExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); + ScheduledExecutorService zkExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); when(client.getState()).thenReturn(CuratorFrameworkState.STARTED); ReflectionTestUtils.setField(zkDiscoveryService, "stopped", false); ReflectionTestUtils.setField(zkDiscoveryService, "client", client); ReflectionTestUtils.setField(zkDiscoveryService, "cache", cache); ReflectionTestUtils.setField(zkDiscoveryService, "nodePath", "/thingsboard/nodes/0000000010"); ReflectionTestUtils.setField(zkDiscoveryService, "zkExecutorService", zkExecutorService); - ReflectionTestUtils.setField(zkDiscoveryService, "recalculateDelay", 1000L); + ReflectionTestUtils.setField(zkDiscoveryService, "recalculateDelay", RECALCULATE_DELAY); ReflectionTestUtils.setField(zkDiscoveryService, "zkDir", "/thingsboard"); - } - - @Test - public void restartNodeTest() throws Exception { - var currentInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("currentId").build(); - var currentData = new ChildData("/thingsboard/nodes/0000000010", null, currentInfo.toByteArray()); - var childInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("childId").build(); - var childData = new ChildData("/thingsboard/nodes/0000000020", null, childInfo.toByteArray()); when(serviceInfoProvider.getServiceInfo()).thenReturn(currentInfo); + List dataList = new ArrayList<>(); dataList.add(currentData); when(cache.getCurrentData()).thenReturn(dataList); + } + @Test + public void restartNodeInTimeTest() throws Exception { startNode(childData); verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); reset(partitionService); - //Restart in timeAssert.assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); stopNode(childData); assertEquals(1, zkDiscoveryService.delayedTasks.size()); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); startNode(childData); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); - Thread.sleep(2000); + Thread.sleep(RECALCULATE_DELAY * 2); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + } + + @Test + public void restartNodeNotInTimeTest() throws Exception { + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); - //Restart not in time stopNode(childData); assertEquals(1, zkDiscoveryService.delayedTasks.size()); - Thread.sleep(2000); + Thread.sleep(RECALCULATE_DELAY * 2); assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); @@ -135,11 +143,19 @@ public class ZkDiscoveryServiceTest { verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); reset(partitionService); + } - //Start another node during restart - var anotherInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("anotherId").build(); + @Test + public void startAnotherNodeDuringRestartTest() throws Exception { + var anotherInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("tb-transport").build(); var anotherData = new ChildData("/thingsboard/nodes/0000000030", null, anotherInfo.toByteArray()); + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); + stopNode(childData); assertEquals(1, zkDiscoveryService.delayedTasks.size()); @@ -151,9 +167,9 @@ public class ZkDiscoveryServiceTest { verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(anotherInfo))); reset(partitionService); - Thread.sleep(2000); + Thread.sleep(RECALCULATE_DELAY * 2); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); startNode(childData); diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index 2c90082eb5..1c567588df 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" queue: type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index aef46a1234..1f8861ced9 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 4bce6e28d7..7c5103cfac 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -68,7 +68,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index eab5b107c8..4ab59aec01 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index f0968aa6b9..a103edf1f4 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index c7dcd70574..44a86dc6dd 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" From b71ae531bb79db83231d051bab8e32e8a53cdea9 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 28 Jul 2023 15:51:21 +0300 Subject: [PATCH 138/168] UI: Clear code and rename state action --- ui-ngx/src/app/core/auth/auth.actions.ts | 8 ++++---- ui-ngx/src/app/core/auth/auth.effects.ts | 4 ++-- ui-ngx/src/app/core/auth/auth.reducer.ts | 2 +- ui-ngx/src/app/core/utils.ts | 4 +++- .../device/device-check-connectivity-dialog.component.ts | 4 ++-- ui-ngx/src/app/shared/components/markdown.component.scss | 2 +- ui-ngx/src/form.scss | 7 ------- 7 files changed, 13 insertions(+), 18 deletions(-) diff --git a/ui-ngx/src/app/core/auth/auth.actions.ts b/ui-ngx/src/app/core/auth/auth.actions.ts index 2e8c82ae2d..9e5640a97d 100644 --- a/ui-ngx/src/app/core/auth/auth.actions.ts +++ b/ui-ngx/src/app/core/auth/auth.actions.ts @@ -27,7 +27,7 @@ export enum AuthActionTypes { UPDATE_LAST_PUBLIC_DASHBOARD_ID = '[Auth] Update Last Public Dashboard Id', UPDATE_HAS_REPOSITORY = '[Auth] Change Has Repository', UPDATE_OPENED_MENU_SECTION = '[Preferences] Update Opened Menu Section', - UPDATE_USER_SETTINGS = '[Preferences] Update user settings', + PUT_USER_SETTINGS = '[Preferences] Put user settings', DELETE_USER_SETTINGS = '[Preferences] Delete user settings', } @@ -71,8 +71,8 @@ export class ActionPreferencesUpdateOpenedMenuSection implements Action { constructor(readonly payload: { path: string; opened: boolean }) {} } -export class ActionPreferencesUpdateUserSettings implements Action { - readonly type = AuthActionTypes.UPDATE_USER_SETTINGS; +export class ActionPreferencesPutUserSettings implements Action { + readonly type = AuthActionTypes.PUT_USER_SETTINGS; constructor(readonly payload: Partial) {} } @@ -85,4 +85,4 @@ export class ActionPreferencesDeleteUserSettings implements Action { export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated | ActionAuthLoadUser | ActionAuthUpdateUserDetails | ActionAuthUpdateLastPublicDashboardId | ActionAuthUpdateHasRepository | - ActionPreferencesUpdateOpenedMenuSection | ActionPreferencesUpdateUserSettings | ActionPreferencesDeleteUserSettings; + ActionPreferencesUpdateOpenedMenuSection | ActionPreferencesPutUserSettings | ActionPreferencesDeleteUserSettings; diff --git a/ui-ngx/src/app/core/auth/auth.effects.ts b/ui-ngx/src/app/core/auth/auth.effects.ts index 76b9dce9fa..3e5eb28d72 100644 --- a/ui-ngx/src/app/core/auth/auth.effects.ts +++ b/ui-ngx/src/app/core/auth/auth.effects.ts @@ -40,9 +40,9 @@ export class AuthEffects { mergeMap(([action, state]) => this.userSettingsService.putUserSettings({ openedMenuSections: state.userSettings.openedMenuSections })) ), {dispatch: false}); - updatedUserSettings = createEffect(() => this.actions$.pipe( + putUserSettings = createEffect(() => this.actions$.pipe( ofType( - AuthActionTypes.UPDATE_USER_SETTINGS, + AuthActionTypes.PUT_USER_SETTINGS, ), mergeMap((state) => this.userSettingsService.putUserSettings(state.payload)) ), {dispatch: false}); diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts index 6fd80d7052..4bcf71104b 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -76,7 +76,7 @@ export const authReducer = ( userSettings = {...state.userSettings, ...{ openedMenuSections: Array.from(openedMenuSections)}}; return { ...state, ...{ userSettings }}; - case AuthActionTypes.UPDATE_USER_SETTINGS: + case AuthActionTypes.PUT_USER_SETTINGS: userSettings = {...state.userSettings, ...action.payload}; return { ...state, ...{ userSettings }}; diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index c823c2bfea..9a369cb5aa 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -355,7 +355,9 @@ const SNAKE_CASE_REGEXP = /[A-Z]/g; export function snakeCase(name: string, separator: string): string { separator = separator || '_'; - return name.replace(SNAKE_CASE_REGEXP, (letter, pos) => (pos ? separator : '') + letter.toLowerCase()); + return name.replace(SNAKE_CASE_REGEXP, (letter, pos) => { + return (pos ? separator : '') + letter.toLowerCase(); + }); } export function getDescendantProp(obj: any, path: string): any { diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts index 2135a3aeea..7516e0f3e1 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts @@ -40,7 +40,7 @@ import { NetworkTransportType, PublishTelemetryCommand } from '@shared/models/device.models'; -import { ActionPreferencesUpdateUserSettings } from '@core/auth/auth.actions'; +import { ActionPreferencesPutUserSettings } from '@core/auth/auth.actions'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { getOS } from '@core/utils'; @@ -121,7 +121,7 @@ export class DeviceCheckConnectivityDialogComponent extends close(): void { if (this.notShowAgain && this.showDontShowAgain) { - this.store.dispatch(new ActionPreferencesUpdateUserSettings({ notDisplayConnectivityAfterAddDevice: true })); + this.store.dispatch(new ActionPreferencesPutUserSettings({ notDisplayConnectivityAfterAddDevice: true })); this.dialogRef.close(null); } else { this.dialogRef.close(null); diff --git a/ui-ngx/src/app/shared/components/markdown.component.scss b/ui-ngx/src/app/shared/components/markdown.component.scss index e23111fc6b..757a26c587 100644 --- a/ui-ngx/src/app/shared/components/markdown.component.scss +++ b/ui-ngx/src/app/shared/components/markdown.component.scss @@ -88,7 +88,7 @@ } } - a:not(.ignore-style-a) { + a { font-weight: 500; color: #2a7dec; text-decoration: none; diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index bb82e937bf..00e9492af0 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -152,13 +152,6 @@ &.space-between { justify-content: space-between; } - &.no-border { - border: none; - border-radius: 0; - } - &.no-padding { - padding: 0; - } .mat-divider-vertical { height: 56px; margin-top: -7px; From 907c8f3e1c644c8a359e9ec704ce9b8fafc3597d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 28 Jul 2023 16:58:12 +0300 Subject: [PATCH 139/168] UI: Optimize gets tabs in routerTabs components --- .../home/components/router-tabs.component.ts | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/router-tabs.component.ts b/ui-ngx/src/app/modules/home/components/router-tabs.component.ts index c5ffb11908..5735499262 100644 --- a/ui-ngx/src/app/modules/home/components/router-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/router-tabs.component.ts @@ -20,8 +20,8 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { MenuService } from '@core/services/menu.service'; -import { distinctUntilChanged, filter, map, mergeMap, take } from 'rxjs/operators'; -import { merge } from 'rxjs'; +import { distinctUntilChanged, filter, map, mergeMap, startWith, take } from 'rxjs/operators'; +import { merge, Observable } from 'rxjs'; import { MenuSection } from '@core/services/menu.models'; import { ActiveComponentService } from '@core/services/active-component.service'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; @@ -39,14 +39,7 @@ export class RouterTabsComponent extends PageComponent implements OnInit { hideCurrentTabs = false; - tabs$ = merge(this.menuService.menuSections(), - this.router.events.pipe( - filter((event) => event instanceof NavigationEnd ), - distinctUntilChanged()) - ).pipe( - mergeMap(() => this.menuService.menuSections().pipe(take(1))), - map((sections) => this.buildTabs(this.activatedRoute, sections)) - ); + tabs$: Observable>; constructor(protected store: Store, private activatedRoute: ActivatedRoute, @@ -57,6 +50,23 @@ export class RouterTabsComponent extends PageComponent implements OnInit { } ngOnInit() { + if (this.activatedRoute.snapshot.data.useChildrenRoutesForTabs) { + this.tabs$ = this.router.events.pipe( + filter((event) => event instanceof NavigationEnd), + startWith(''), + map(() => this.buildTabsForRoutes(this.activatedRoute)) + ); + } else { + this.tabs$ = merge(this.menuService.menuSections(), + this.router.events.pipe( + filter((event) => event instanceof NavigationEnd ), + distinctUntilChanged()) + ).pipe( + mergeMap(() => this.menuService.menuSections().pipe(take(1))), + map((sections) => this.buildTabs(this.activatedRoute, sections)) + ); + } + this.activatedRoute.data.subscribe( (data) => this.buildTabsHeaderComponent(data) ); @@ -80,16 +90,26 @@ export class RouterTabsComponent extends PageComponent implements OnInit { } } - private buildTabs(activatedRoute: ActivatedRoute, sections: MenuSection[]): Array { - const sectionPath = '/' + activatedRoute.pathFromRoot.map(r => r.snapshot.url) + private getSectionPath(activatedRoute: ActivatedRoute): string { + return '/' + activatedRoute.pathFromRoot.map(r => r.snapshot.url) .filter(f => !!f[0]).map(f => f.map(f1 => f1.path).join('/')).join('/'); + } + + private buildTabs(activatedRoute: ActivatedRoute, sections: MenuSection[]): Array { + const sectionPath = this.getSectionPath(activatedRoute); const found = this.findRootSection(sections, sectionPath); if (found) { const rootPath = sectionPath.substring(0, sectionPath.length - found.path.length); const isRoot = rootPath === ''; const tabs: Array = found ? found.pages.filter(page => !page.disabled && (!page.rootOnly || isRoot)) : []; return tabs.map((tab) => ({...tab, path: rootPath + tab.path})); - } else if (activatedRoute.snapshot.data.useChildrenRoutesForTabs && sectionPath.endsWith(activatedRoute.routeConfig.path)) { + } + return []; + } + + private buildTabsForRoutes(activatedRoute: ActivatedRoute): Array { + const sectionPath = this.getSectionPath(activatedRoute); + if (activatedRoute.routeConfig.children.length) { const activeRouterChildren = activatedRoute.routeConfig.children.filter(page => page.path !== ''); return activeRouterChildren.map(tab => ({ id: tab.component.name, @@ -98,9 +118,8 @@ export class RouterTabsComponent extends PageComponent implements OnInit { icon: tab.data?.breadcrumb?.icon ?? '', path: `${sectionPath}/${tab.path}` })); - } else { - return []; } + return []; } private findRootSection(sections: MenuSection[], sectionPath: string): MenuSection { From 5b2918de9589bbdd763dbfe1317a5b3c11d869a4 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 28 Jul 2023 17:27:38 +0200 Subject: [PATCH 140/168] minor improvements --- .../thingsboard/server/controller/BaseController.java | 4 ---- .../server/controller/DeviceConnectivityController.java | 9 +++++---- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 77aa31df20..68a987a0bc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -113,7 +113,6 @@ import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.ClaimDevicesService; -import org.thingsboard.server.dao.device.DeviceConnectivityService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; @@ -209,9 +208,6 @@ public abstract class BaseController { @Autowired protected DeviceService deviceService; - @Autowired - protected DeviceConnectivityService deviceConnectivityService; - @Autowired protected DeviceProfileService deviceProfileService; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java index b9b12da17d..04b1b4c522 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java @@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.dao.device.DeviceConnectivityService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.system.SystemSecurityService; @@ -46,7 +47,6 @@ import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PROTOCOL; import static org.thingsboard.server.controller.ControllerConstants.PROTOCOL_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.PEM_CERT_FILE_NAME; @@ -57,6 +57,7 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.PEM_CERT_FI @Slf4j public class DeviceConnectivityController extends BaseController { + private final DeviceConnectivityService deviceConnectivityService; private final SystemSecurityService systemSecurityService; @ApiOperation(value = "Get commands to publish device telemetry (getDevicePublishTelemetryCommands)", @@ -86,11 +87,11 @@ public class DeviceConnectivityController extends BaseController { return deviceConnectivityService.findDevicePublishTelemetryCommands(baseUrl, device); } - @ApiOperation(value = "Download mqtt ssl certificate using file path defined in device.connectivity properties (downloadMqttServerCertificate)", notes = "Download Mqtt server certificate." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Download server certificate using file path defined in device.connectivity properties (downloadServerCertificate)", notes = "Download server certificate.") @RequestMapping(value = "/device-connectivity/{protocol}/certificate/download", method = RequestMethod.GET) @ResponseBody - public ResponseEntity downloadMqttServerCertificate(@ApiParam(value = PROTOCOL_PARAM_DESCRIPTION) - @PathVariable(PROTOCOL) String protocol) throws ThingsboardException, IOException { + public ResponseEntity downloadServerCertificate(@ApiParam(value = PROTOCOL_PARAM_DESCRIPTION) + @PathVariable(PROTOCOL) String protocol) throws ThingsboardException, IOException { checkParameter(PROTOCOL, protocol); var pemCert = checkNotNull(deviceConnectivityService.getPemCertFile(protocol), protocol + " pem cert file is not found!"); 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 18e97ecef1..27c6768980 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1397,7 +1397,7 @@ "device-created-check-connectivity": "Device created. Let's check connectivity!", "loading-check-connectivity-command": "Loading check connectivity commands...", "use-following-instructions": "Use the following instructions for sending telemetry on behalf of the device using shell", - "execute-following-command": "Executive the following command", + "execute-following-command": "Execute the following command", "install-curl-windows": "Starting Windows 10 b17063, cURL is available by default", "install-mqtt-windows": "Use the instructions to download, install, setup and run mosquitto_pub", "install-coap-client": "Use the instructions to download, install, setup and run coap-client", From 49b149d484e99c802715eb81283ab245f2ee25f2 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 28 Jul 2023 18:32:36 +0300 Subject: [PATCH 141/168] UI: Refactoring for new style --- .../lib/multiple-input-widget.component.html | 42 ++++++++++++------- .../lib/multiple-input-widget.component.scss | 26 +++++++++++- .../components/color-input.component.ts | 5 ++- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index fa51899c4a..9c228ec93f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -28,7 +28,7 @@
- + {{key.label}}
- + {{key.label}}
- + {{key.label}} - + {{key.label}}
- + {{key.label}}
- - + +
+
+ + {{key.settings.icon}} + + icon + + + {{key.label}} +
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss index d0dd324e52..8fec24cf05 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss @@ -21,7 +21,7 @@ flex-direction: column; .tb-multiple-input-container { - padding: 0 8px; + padding: 8px 8px 0; flex: 1 1 100%; overflow-x: hidden; overflow-y: auto; @@ -37,6 +37,30 @@ } } + .color-picker-input { + height: 56px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 7px 16px 7px 12px; + margin: 0 10px 22px 0; + border: 1px solid rgba(0, 0, 0, 0.4); + border-radius: 6px; + + .mat-icon, img { + margin-right: 5px; + } + + .mat-divider-vertical { + height: 56px; + margin-top: -7px; + margin-bottom: -7px; + border-right-color: rgba(0, 0, 0, 0.4); + } + } + .input-field { padding-right: 10px; diff --git a/ui-ngx/src/app/shared/components/color-input.component.ts b/ui-ngx/src/app/shared/components/color-input.component.ts index fa6c73116e..88a49f756e 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.ts +++ b/ui-ngx/src/app/shared/components/color-input.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -91,6 +91,8 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro @Input() disabled: boolean; + @Output() colorChanged: EventEmitter = new EventEmitter(); + private modelValue: string; private propagateChange = null; @@ -150,6 +152,7 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro if (this.modelValue !== color) { this.modelValue = color; this.propagateChange(this.modelValue); + this.colorChanged.emit(color); } } From 08bd89d0bee76a86b51244ef3548a64b5dd6e423 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 28 Jul 2023 18:44:22 +0300 Subject: [PATCH 142/168] UI: Remove divider --- .../widget/lib/multiple-input-widget.component.html | 1 - .../widget/lib/multiple-input-widget.component.scss | 7 ------- 2 files changed, 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 67c28bd878..6c749cab3b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -184,7 +184,6 @@ {{key.label}}
- Date: Mon, 31 Jul 2023 07:56:31 +0300 Subject: [PATCH 143/168] Fix for removing user from sysadmin level alarm unassignment --- .../entitiy/user/DefaultUserService.java | 2 +- .../controller/AlarmControllerTest.java | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index 0c04e46ff5..d9f11dacb5 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -82,7 +82,7 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse UserId userId = tbUser.getId(); try { - tbAlarmService.unassignUserAlarms(tenantId, tbUser, System.currentTimeMillis()); + tbAlarmService.unassignUserAlarms(tbUser.getTenantId(), tbUser, System.currentTimeMillis()); userService.deleteUser(tenantId, userId); notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, customerId, userId, tbUser, user, ActionType.DELETED, true, null, customerId.toString()); diff --git a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java index 50761be096..6ce6e22e9a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java @@ -531,6 +531,55 @@ public class AlarmControllerTest extends AbstractControllerTest { tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_UNASSIGNED); } + @Test + public void testUnassignTenantUserAlarmOnUserRemoving() throws Exception { + loginDifferentTenant(); + + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(tenantId); + user.setEmail("tenantForAssign@thingsboard.org"); + User savedUser = createUser(user, "password"); + + Device device = createDevice("Different tenant device", "default", "differentTenantTest"); + + Alarm alarm = Alarm.builder() + .type(TEST_ALARM_TYPE) + .tenantId(savedDifferentTenant.getId()) + .originator(device.getId()) + .severity(AlarmSeverity.MAJOR) + .build(); + alarm = doPost("/api/alarm", alarm, Alarm.class); + Assert.assertNotNull(alarm); + + alarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); + Assert.assertNotNull(alarm); + + Mockito.reset(tbClusterService, auditLogService); + long beforeAssignmentTs = System.currentTimeMillis(); + + doPost("/api/alarm/" + alarm.getId() + "/assign/" + savedUser.getId().getId()).andExpect(status().isOk()); + AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); + Assert.assertNotNull(foundAlarm); + Assert.assertEquals(savedUser.getId(), foundAlarm.getAssigneeId()); + Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs); + + beforeAssignmentTs = System.currentTimeMillis(); + + Mockito.reset(tbClusterService, auditLogService); + + loginSysAdmin(); + + doDelete("/api/user/" + savedUser.getId().getId()).andExpect(status().isOk()); + + loginDifferentTenant(); + + foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); + Assert.assertNotNull(foundAlarm); + Assert.assertNull(foundAlarm.getAssigneeId()); + Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs); + } + @Test public void testUnassignAlarmOnUserRemoving() throws Exception { loginDifferentTenant(); From 037dbd25d07b2a45699d9752b012d3a5f1660625 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Mon, 31 Jul 2023 09:28:08 +0300 Subject: [PATCH 144/168] Enabled test with this message for OUT messages with errors --- .../src/app/modules/home/components/event/event-table-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index 026624dbf3..d574a3593a 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -360,7 +360,7 @@ export class EventTableConfig extends EntityTableConfig { this.cellActionDescriptors.push({ name: this.translate.instant('rulenode.test-with-this-message', {test: this.translate.instant(this.testButtonLabel)}), icon: 'bug_report', - isEnabled: (entity) => entity.body.type === 'IN', + isEnabled: (entity) => entity.body.type === 'IN' || entity.body.error !== undefined, onAction: ($event, entity) => { this.debugEventSelected.next(entity.body); } From 054b1901448f2d48abaeb9ad13d786f027dbbfe2 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 31 Jul 2023 12:25:48 +0300 Subject: [PATCH 145/168] UI: Add routes tab settings replaceUrl --- .../modules/home/components/router-tabs.component.html | 1 + .../modules/home/components/router-tabs.component.ts | 6 ++++++ ui-ngx/src/app/modules/home/home.component.ts | 9 ++++----- .../home/pages/account/account-routing.module.ts | 10 ++++++++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/router-tabs.component.html b/ui-ngx/src/app/modules/home/components/router-tabs.component.html index f16a761c3e..5ad09403c2 100644 --- a/ui-ngx/src/app/modules/home/components/router-tabs.component.html +++ b/ui-ngx/src/app/modules/home/components/router-tabs.component.html @@ -20,6 +20,7 @@
-
-
-
+
+
{{ 'widgets.input-widgets.no-entity-selected' | translate }}
-
+
{{ 'widgets.input-widgets.not-allowed-entity' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss index 7a27c39907..f6edd1fb52 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss @@ -37,17 +37,22 @@ } } - .color-picker-input { - height: 56px; + .tb-multiple-input-layout { display: flex; flex-direction: row; - align-items: center; - justify-content: space-between; - gap: 16px; + align-items: start; + } + + .color-picker-input { padding: 7px 16px 7px 12px; margin: 0 10px 22px 0; - border: 1px solid rgba(0, 0, 0, 0.4); - border-radius: 6px; + border-color: rgba(0, 0, 0, 0.4); + + .label-container { + display: flex; + flex-direction: row; + align-items: center; + } .mat-icon, img { margin-right: 5px; @@ -78,6 +83,30 @@ .vertical-alignment { flex-direction: column; } + + &--buttons-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: end; + &__button { + max-height: 50px; + margin-right:20px; + } + } + + &__errors { + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + &__error { + text-align: center; + font-size: 18px; + color: #a0a0a0; + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index 4c7fb1cfec..6f29a495cf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -390,6 +390,12 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } }); } + } else if (key.settings.dataKeyValueType === 'color') { + formControl.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { + this.inputChanged(source, key); + }); } this.multipleInputFormGroup.addControl(key.formId, formControl); } diff --git a/ui-ngx/src/app/shared/components/color-input.component.ts b/ui-ngx/src/app/shared/components/color-input.component.ts index 8997409e7c..f22b91fde2 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.ts +++ b/ui-ngx/src/app/shared/components/color-input.component.ts @@ -14,17 +14,7 @@ /// limitations under the License. /// -import { - ChangeDetectorRef, - Component, - EventEmitter, - forwardRef, - Input, - OnInit, - Output, - Renderer2, - ViewContainerRef -} from '@angular/core'; +import { ChangeDetectorRef, Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -110,8 +100,6 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro @Input() disabled: boolean; - @Output() colorChanged: EventEmitter = new EventEmitter(); - private modelValue: string; private propagateChange = null; @@ -174,7 +162,6 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro if (this.modelValue !== color) { this.modelValue = color; this.propagateChange(this.modelValue); - this.colorChanged.emit(color); } } From 1569bee351715f203cb141377050e96d0fd3797c Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 31 Jul 2023 13:34:37 +0300 Subject: [PATCH 149/168] UI: Refactoring error container --- .../widget/lib/multiple-input-widget.component.html | 6 +++--- .../widget/lib/multiple-input-widget.component.scss | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 39b135b77a..ae739332be 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -207,11 +207,11 @@ {{ saveButtonLabel }}
-
-
+
+
{{ 'widgets.input-widgets.no-entity-selected' | translate }}
-
+
{{ 'widgets.input-widgets.not-allowed-entity' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss index f6edd1fb52..3185bc8b17 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss @@ -95,17 +95,17 @@ } } - &__errors { + &--errors-container { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; - } - &__error { - text-align: center; - font-size: 18px; - color: #a0a0a0; + &__error { + text-align: center; + font-size: 18px; + color: #a0a0a0; + } } } } From 68149d96739ed1445f3ad3c25c622ea72dc7810b Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 16 Jun 2023 15:40:29 +0200 Subject: [PATCH 150/168] added recalculetePartitions delay for node restart --- .../src/main/resources/thingsboard.yml | 1 + .../queue/discovery/ZkDiscoveryService.java | 31 ++++++++++++++++++- .../src/main/resources/tb-vc-executor.yml | 1 + .../src/main/resources/tb-coap-transport.yml | 1 + .../src/main/resources/tb-http-transport.yml | 1 + .../src/main/resources/tb-lwm2m-transport.yml | 1 + .../src/main/resources/tb-mqtt-transport.yml | 1 + .../src/main/resources/tb-snmp-transport.yml | 1 + 8 files changed, 37 insertions(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 3666678561..19804c0588 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -96,6 +96,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cluster: stats: diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index fcf80bcf3d..17d046a4cb 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -44,8 +44,10 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.List; import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; 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; @@ -66,6 +68,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private Integer zkSessionTimeout; @Value("${zk.zk_dir}") private String zkDir; + @Value("${zk.recalculate_delay:120000}") + private Long recalculateDelay; + + private final ConcurrentHashMap> delayedTasks; private final TbServiceInfoProvider serviceInfoProvider; private final PartitionService partitionService; @@ -82,6 +88,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi PartitionService partitionService) { this.serviceInfoProvider = serviceInfoProvider; this.partitionService = partitionService; + delayedTasks = new ConcurrentHashMap<>(); } @PostConstruct @@ -290,8 +297,30 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi log.debug("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), instance.getServiceId()); switch (pathChildrenCacheEvent.getType()) { case CHILD_ADDED: + ScheduledFuture task = delayedTasks.remove(instance.getServiceId()); + if (task != null) { + if (!task.cancel(false)) { + log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + recalculatePartitions(); + } else { + log.debug("[{}] Recalculate partitions ignored. Service restarted in time [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + } + } else { + log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + recalculatePartitions(); + } + break; case CHILD_REMOVED: - recalculatePartitions(); + ScheduledFuture future = zkExecutorService.schedule(() -> { + log.debug("[{}] Going to recalculate partitions due to removed node [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + delayedTasks.remove(instance.getServiceId()); + recalculatePartitions(); + }, recalculateDelay, TimeUnit.MILLISECONDS); + delayedTasks.put(instance.getServiceId(), future); break; default: break; diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index 352f94e091..0dbb19a71a 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" queue: type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 7ea553fe5c..c8f4b5a099 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 346ec48eae..fe181f12f2 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -68,6 +68,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 4e8167d89d..d80279f582 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index 1e0b1ebcd4..fcbf542287 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index 9f086bcbc5..0e84d54fce 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" From ce9552e1a8ca44f58a369051bfc9f5bc24ca1477 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 6 Jul 2023 13:31:25 +0200 Subject: [PATCH 151/168] improvements --- .../queue/discovery/ZkDiscoveryService.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 17d046a4cb..24a7863b24 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -299,16 +299,16 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi case CHILD_ADDED: ScheduledFuture task = delayedTasks.remove(instance.getServiceId()); if (task != null) { - if (!task.cancel(false)) { - log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + if (task.cancel(false)) { + log.debug("[{}] Recalculate partitions ignored. Service was restarted in time [{}].", instance.getServiceId(), instance.getServiceTypesList()); - recalculatePartitions(); } else { - log.debug("[{}] Recalculate partitions ignored. Service restarted in time [{}]", + log.debug("[{}] Going to recalculate partitions. Service was not restarted in time [{}]!", instance.getServiceId(), instance.getServiceTypesList()); + recalculatePartitions(); } } else { - log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + log.debug("[{}] Going to recalculate partitions due to adding new node [{}].", instance.getServiceId(), instance.getServiceTypesList()); recalculatePartitions(); } @@ -317,8 +317,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi ScheduledFuture future = zkExecutorService.schedule(() -> { log.debug("[{}] Going to recalculate partitions due to removed node [{}]", instance.getServiceId(), instance.getServiceTypesList()); - delayedTasks.remove(instance.getServiceId()); - recalculatePartitions(); + ScheduledFuture removedTask = delayedTasks.remove(instance.getServiceId()); + if (removedTask != null) { + recalculatePartitions(); + } }, recalculateDelay, TimeUnit.MILLISECONDS); delayedTasks.put(instance.getServiceId(), future); break; @@ -332,6 +334,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi * Synchronized to ensure that other servers info is up to date * */ synchronized void recalculatePartitions() { + delayedTasks.clear(); partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); } From 948f517898ff2207e6ba797e83ca2f77a3194790 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 14 Jul 2023 19:45:23 +0200 Subject: [PATCH 152/168] added zk restart node tests --- .../queue/discovery/ZkDiscoveryService.java | 2 +- .../discovery/ZkDiscoveryServiceTest.java | 173 ++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 24a7863b24..50378d3387 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -71,7 +71,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi @Value("${zk.recalculate_delay:120000}") private Long recalculateDelay; - private final ConcurrentHashMap> delayedTasks; + protected final ConcurrentHashMap> delayedTasks; private final TbServiceInfoProvider serviceInfoProvider; private final PartitionService partitionService; diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java new file mode 100644 index 0000000000..38cad217aa --- /dev/null +++ b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java @@ -0,0 +1,173 @@ +/** + * 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.discovery; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.imps.CuratorFrameworkState; +import org.apache.curator.framework.recipes.cache.ChildData; +import org.apache.curator.framework.recipes.cache.PathChildrenCache; +import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_ADDED; +import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ZkDiscoveryServiceTest { + + @Mock + private TbServiceInfoProvider serviceInfoProvider; + + @Mock + private PartitionService partitionService; + + @Mock + private CuratorFramework client; + + @Mock + private PathChildrenCache cache; + + private ScheduledExecutorService zkExecutorService; + + @Mock + private CuratorFramework curatorFramework; + + private ZkDiscoveryService zkDiscoveryService; + + @Before + public void setup() { + zkDiscoveryService = Mockito.spy(new ZkDiscoveryService(serviceInfoProvider, partitionService)); + zkExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); + when(client.getState()).thenReturn(CuratorFrameworkState.STARTED); + ReflectionTestUtils.setField(zkDiscoveryService, "stopped", false); + ReflectionTestUtils.setField(zkDiscoveryService, "client", client); + ReflectionTestUtils.setField(zkDiscoveryService, "cache", cache); + ReflectionTestUtils.setField(zkDiscoveryService, "nodePath", "/thingsboard/nodes/0000000010"); + ReflectionTestUtils.setField(zkDiscoveryService, "zkExecutorService", zkExecutorService); + ReflectionTestUtils.setField(zkDiscoveryService, "recalculateDelay", 1000L); + ReflectionTestUtils.setField(zkDiscoveryService, "zkDir", "/thingsboard"); + } + + @Test + public void restartNodeTest() throws Exception { + var currentInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("currentId").build(); + var currentData = new ChildData("/thingsboard/nodes/0000000010", null, currentInfo.toByteArray()); + var childInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("childId").build(); + var childData = new ChildData("/thingsboard/nodes/0000000020", null, childInfo.toByteArray()); + + when(serviceInfoProvider.getServiceInfo()).thenReturn(currentInfo); + List dataList = new ArrayList<>(); + dataList.add(currentData); + when(cache.getCurrentData()).thenReturn(dataList); + + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); + + //Restart in timeAssert.assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + stopNode(childData); + + assertEquals(1, zkDiscoveryService.delayedTasks.size()); + + verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + + startNode(childData); + + verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + + Thread.sleep(2000); + + verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + + assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + + //Restart not in time + stopNode(childData); + + assertEquals(1, zkDiscoveryService.delayedTasks.size()); + + Thread.sleep(2000); + + assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(Collections.emptyList())); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); + + //Start another node during restart + var anotherInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("anotherId").build(); + var anotherData = new ChildData("/thingsboard/nodes/0000000030", null, anotherInfo.toByteArray()); + + stopNode(childData); + + assertEquals(1, zkDiscoveryService.delayedTasks.size()); + + startNode(anotherData); + + assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(anotherInfo))); + reset(partitionService); + + Thread.sleep(2000); + + verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(anotherInfo, childInfo))); + } + + private void startNode(ChildData data) throws Exception { + cache.getCurrentData().add(data); + zkDiscoveryService.childEvent(curatorFramework, new PathChildrenCacheEvent(CHILD_ADDED, data)); + } + + private void stopNode(ChildData data) throws Exception { + cache.getCurrentData().remove(data); + zkDiscoveryService.childEvent(curatorFramework, new PathChildrenCacheEvent(CHILD_REMOVED, data)); + } + +} From ac2aac8aa7a264e8ff9452714818cd1dfcc9ba00 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 28 Jul 2023 14:20:33 +0200 Subject: [PATCH 153/168] refactored due to comments --- .../src/main/resources/thingsboard.yml | 2 +- .../queue/discovery/ZkDiscoveryService.java | 26 +++++--- .../discovery/ZkDiscoveryServiceTest.java | 62 ++++++++++++------- .../src/main/resources/tb-vc-executor.yml | 2 +- .../src/main/resources/tb-coap-transport.yml | 2 +- .../src/main/resources/tb-http-transport.yml | 2 +- .../src/main/resources/tb-lwm2m-transport.yml | 2 +- .../src/main/resources/tb-mqtt-transport.yml | 2 +- .../src/main/resources/tb-snmp-transport.yml | 2 +- 9 files changed, 62 insertions(+), 40 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 19804c0588..1f16fbc414 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -96,7 +96,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cluster: stats: diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 50378d3387..44999d016a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.queue.discovery; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.ProtocolStringList; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.curator.framework.CuratorFramework; @@ -68,7 +69,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private Integer zkSessionTimeout; @Value("${zk.zk_dir}") private String zkDir; - @Value("${zk.recalculate_delay:120000}") + @Value("${zk.recalculate_delay:60000}") private Long recalculateDelay; protected final ConcurrentHashMap> delayedTasks; @@ -294,35 +295,39 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi log.error("Failed to decode server instance for node {}", data.getPath(), e); throw e; } - log.debug("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), instance.getServiceId()); + + String serviceId = instance.getServiceId(); + ProtocolStringList serviceTypesList = instance.getServiceTypesList(); + + log.trace("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), serviceId); switch (pathChildrenCacheEvent.getType()) { case CHILD_ADDED: - ScheduledFuture task = delayedTasks.remove(instance.getServiceId()); + ScheduledFuture task = delayedTasks.remove(serviceId); if (task != null) { if (task.cancel(false)) { log.debug("[{}] Recalculate partitions ignored. Service was restarted in time [{}].", - instance.getServiceId(), instance.getServiceTypesList()); + serviceId, serviceTypesList); } else { log.debug("[{}] Going to recalculate partitions. Service was not restarted in time [{}]!", - instance.getServiceId(), instance.getServiceTypesList()); + serviceId, serviceTypesList); recalculatePartitions(); } } else { - log.debug("[{}] Going to recalculate partitions due to adding new node [{}].", - instance.getServiceId(), instance.getServiceTypesList()); + log.trace("[{}] Going to recalculate partitions due to adding new node [{}].", + serviceId, serviceTypesList); recalculatePartitions(); } break; case CHILD_REMOVED: ScheduledFuture future = zkExecutorService.schedule(() -> { log.debug("[{}] Going to recalculate partitions due to removed node [{}]", - instance.getServiceId(), instance.getServiceTypesList()); - ScheduledFuture removedTask = delayedTasks.remove(instance.getServiceId()); + serviceId, serviceTypesList); + ScheduledFuture removedTask = delayedTasks.remove(serviceId); if (removedTask != null) { recalculatePartitions(); } }, recalculateDelay, TimeUnit.MILLISECONDS); - delayedTasks.put(instance.getServiceId(), future); + delayedTasks.put(serviceId, future); break; default: break; @@ -334,6 +339,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi * Synchronized to ensure that other servers info is up to date * */ synchronized void recalculatePartitions() { + delayedTasks.values().forEach(future -> future.cancel(false)); delayedTasks.clear(); partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); } diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java index 38cad217aa..a8810efd0e 100644 --- a/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java +++ b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java @@ -63,68 +63,76 @@ public class ZkDiscoveryServiceTest { @Mock private PathChildrenCache cache; - private ScheduledExecutorService zkExecutorService; - @Mock private CuratorFramework curatorFramework; private ZkDiscoveryService zkDiscoveryService; + private static final long RECALCULATE_DELAY = 100L; + + final TransportProtos.ServiceInfo currentInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("tb-rule-engine-0").build(); + final ChildData currentData = new ChildData("/thingsboard/nodes/0000000010", null, currentInfo.toByteArray()); + final TransportProtos.ServiceInfo childInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("tb-rule-engine-1").build(); + final ChildData childData = new ChildData("/thingsboard/nodes/0000000020", null, childInfo.toByteArray()); + @Before public void setup() { zkDiscoveryService = Mockito.spy(new ZkDiscoveryService(serviceInfoProvider, partitionService)); - zkExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); + ScheduledExecutorService zkExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); when(client.getState()).thenReturn(CuratorFrameworkState.STARTED); ReflectionTestUtils.setField(zkDiscoveryService, "stopped", false); ReflectionTestUtils.setField(zkDiscoveryService, "client", client); ReflectionTestUtils.setField(zkDiscoveryService, "cache", cache); ReflectionTestUtils.setField(zkDiscoveryService, "nodePath", "/thingsboard/nodes/0000000010"); ReflectionTestUtils.setField(zkDiscoveryService, "zkExecutorService", zkExecutorService); - ReflectionTestUtils.setField(zkDiscoveryService, "recalculateDelay", 1000L); + ReflectionTestUtils.setField(zkDiscoveryService, "recalculateDelay", RECALCULATE_DELAY); ReflectionTestUtils.setField(zkDiscoveryService, "zkDir", "/thingsboard"); - } - - @Test - public void restartNodeTest() throws Exception { - var currentInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("currentId").build(); - var currentData = new ChildData("/thingsboard/nodes/0000000010", null, currentInfo.toByteArray()); - var childInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("childId").build(); - var childData = new ChildData("/thingsboard/nodes/0000000020", null, childInfo.toByteArray()); when(serviceInfoProvider.getServiceInfo()).thenReturn(currentInfo); + List dataList = new ArrayList<>(); dataList.add(currentData); when(cache.getCurrentData()).thenReturn(dataList); + } + @Test + public void restartNodeInTimeTest() throws Exception { startNode(childData); verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); reset(partitionService); - //Restart in timeAssert.assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); stopNode(childData); assertEquals(1, zkDiscoveryService.delayedTasks.size()); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); startNode(childData); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); - Thread.sleep(2000); + Thread.sleep(RECALCULATE_DELAY * 2); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + } + + @Test + public void restartNodeNotInTimeTest() throws Exception { + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); - //Restart not in time stopNode(childData); assertEquals(1, zkDiscoveryService.delayedTasks.size()); - Thread.sleep(2000); + Thread.sleep(RECALCULATE_DELAY * 2); assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); @@ -135,11 +143,19 @@ public class ZkDiscoveryServiceTest { verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); reset(partitionService); + } - //Start another node during restart - var anotherInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("anotherId").build(); + @Test + public void startAnotherNodeDuringRestartTest() throws Exception { + var anotherInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("tb-transport").build(); var anotherData = new ChildData("/thingsboard/nodes/0000000030", null, anotherInfo.toByteArray()); + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); + stopNode(childData); assertEquals(1, zkDiscoveryService.delayedTasks.size()); @@ -151,9 +167,9 @@ public class ZkDiscoveryServiceTest { verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(anotherInfo))); reset(partitionService); - Thread.sleep(2000); + Thread.sleep(RECALCULATE_DELAY * 2); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); startNode(childData); diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index 0dbb19a71a..66c6b4d3da 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" queue: type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index c8f4b5a099..f4b5e0bc94 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index fe181f12f2..f92da86b99 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -68,7 +68,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index d80279f582..05388473f0 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index fcbf542287..e131788929 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index 0e84d54fce..a7928eb49f 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" From 20db421a8aefba1109d75524e7814d3cb5dd4199 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 31 Jul 2023 14:19:34 +0300 Subject: [PATCH 154/168] UI: Implement pagination support on overflow for toggle select/header component. --- .../add-widget-dialog.component.html | 2 +- .../dashboard-page.component.html | 2 +- .../components/toggle-header.component.html | 33 +++- .../components/toggle-header.component.scss | 26 +++ .../components/toggle-header.component.ts | 179 +++++++++++++++++- .../components/toggle-select.component.html | 1 + .../components/toggle-select.component.ts | 9 +- 7 files changed, 238 insertions(+), 14 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html index 7a17157b31..7de7acf413 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html @@ -20,7 +20,7 @@

widget.add

: {{data.widgetInfo.widgetName}}
- + {{ 'widget.basic-mode' | translate }} {{ 'widget.advanced-mode' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html index 6432a3f234..b627783d47 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html @@ -360,7 +360,7 @@ [isReadOnly]="true" (closeDetails)="onEditWidgetClosed()">
- + {{ 'widget.basic-mode' | translate }} {{ 'widget.advanced-mode' | translate }} diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.html b/ui-ngx/src/app/shared/components/toggle-header.component.html index d7ed76de90..c2136558e3 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.html +++ b/ui-ngx/src/app/shared/components/toggle-header.component.html @@ -15,14 +15,31 @@ limitations under the License. --> - - {{ option.name }} - + +
+ + {{ option.name }} + +
+ diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.scss b/ui-ngx/src/app/shared/components/toggle-header.component.scss index 6a6785c11b..dd983f3de9 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.scss +++ b/ui-ngx/src/app/shared/components/toggle-header.component.scss @@ -17,8 +17,34 @@ @import "../../../theme"; @import "../../../scss/constants"; +:host { + max-width: 100%; + display: grid; + grid-template-columns: min-content minmax(auto, 1fr) min-content; + .tb-toggle-header-pagination-button { + display: none; + } + &.tb-toggle-header-pagination-controls-enabled { + .tb-toggle-header-pagination-button { + display: block; + } + } + .tb-toggle-container { + display: inline-grid; + grid-column: 2; + overflow: hidden; + &.tb-disable-pagination { + overflow: visible; + } + } + .tb-toggle-header { + transition: transform 500ms cubic-bezier(0.35, 0, 0.25, 1); + } +} + :host ::ng-deep { .mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header { + overflow: visible; width: 100%; border-radius: 100px; height: 32px; diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.ts b/ui-ngx/src/app/shared/components/toggle-header.component.ts index 35daad0e3f..6599a6fe35 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.ts +++ b/ui-ngx/src/app/shared/components/toggle-header.component.ts @@ -15,18 +15,22 @@ /// import { + AfterContentChecked, AfterContentInit, + AfterViewInit, ChangeDetectorRef, Component, ContentChildren, Directive, ElementRef, EventEmitter, + HostBinding, Input, OnDestroy, OnInit, Output, - QueryList + QueryList, + ViewChild } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; @@ -36,6 +40,8 @@ import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { coerceBoolean } from '@shared/decorators/coercion'; import { startWith, takeUntil } from 'rxjs/operators'; +import { Platform } from '@angular/cdk/platform'; +import { MatButtonToggle, MatButtonToggleGroup } from '@angular/material/button-toggle'; export interface ToggleHeaderOption { name: string; @@ -44,6 +50,8 @@ export interface ToggleHeaderOption { export type ToggleHeaderAppearance = 'fill' | 'fill-invert' | 'stroked'; +export type ScrollDirection = 'after' | 'before'; + @Directive( { // eslint-disable-next-line @angular-eslint/directive-selector @@ -72,7 +80,7 @@ export abstract class _ToggleBase extends PageComponent implements AfterContentI @Input() options: ToggleHeaderOption[] = []; - private _destroyed = new Subject(); + protected _destroyed = new Subject(); protected constructor(protected store: Store) { super(store); @@ -109,7 +117,34 @@ export abstract class _ToggleBase extends PageComponent implements AfterContentI templateUrl: './toggle-header.component.html', styleUrls: ['./toggle-header.component.scss'] }) -export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterContentInit, OnDestroy { +export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterViewInit, AfterContentInit, AfterContentChecked, OnDestroy { + + @ViewChild('toggleGroup', {static: false}) + toggleGroup: ElementRef; + + @ViewChild(MatButtonToggleGroup, {static: false}) + buttonToggleGroup: MatButtonToggleGroup; + + @ViewChild('toggleGroupContainer', {static: false}) + toggleGroupContainer: ElementRef; + + @HostBinding('class.tb-toggle-header-pagination-controls-enabled') + private showPaginationControls = false; + + private toggleGroupResize$: ResizeObserver; + + leftPaginationEnabled = false; + rightPaginationEnabled = false; + + private _scrollDistance = 0; + private _scrollDistanceChanged: boolean; + + get scrollDistance(): number { + return this._scrollDistance; + } + set scrollDistance(value: number) { + this._scrollTo(value); + } @Input() value: any; @@ -120,6 +155,10 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterC @Input() name: string; + @Input() + @coerceBoolean() + disablePagination = false; + @Input() @coerceBoolean() useSelectOnMdLg = true; @@ -141,6 +180,7 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterC constructor(protected store: Store, private cd: ChangeDetectorRef, + private platform: Platform, private breakpointObserver: BreakpointObserver) { super(store); } @@ -154,9 +194,142 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterC this.cd.markForCheck(); } ); + if (!this.disablePagination) { + this.valueChange.pipe(takeUntil(this._destroyed)).subscribe(() => { + this.scrollToToggleOptionValue(); + }); + } + } + + ngOnDestroy() { + if (this.toggleGroupResize$) { + this.toggleGroupResize$.disconnect(); + } + super.ngOnDestroy(); + } + + ngAfterViewInit() { + if (!this.disablePagination && !this.useSelectOnMdLg) { + this.toggleGroupResize$ = new ResizeObserver(() => { + this.updatePagination(); + }); + this.toggleGroupResize$.observe(this.toggleGroupContainer.nativeElement); + } + } + + ngAfterContentChecked() { + if (this._scrollDistanceChanged) { + this.updateToggleHeaderScrollPosition(); + this._scrollDistanceChanged = false; + this.cd.markForCheck(); + } } trackByHeaderOption(index: number, option: ToggleHeaderOption){ return option.value; } + + handlePaginatorClick(direction: ScrollDirection, $event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.scrollHeader(direction); + } + + handlePaginatorTouchStart(direction: ScrollDirection, $event: Event) { + if (direction === 'before' && !this.leftPaginationEnabled || + direction === 'after' && !this.rightPaginationEnabled) { + $event.preventDefault(); + } + } + + private scrollHeader(direction: ScrollDirection) { + const viewLength = this.toggleGroup.nativeElement.offsetWidth; + // Move the scroll distance one-third the length of the tab list's viewport. + const scrollAmount = ((direction === 'before' ? -1 : 1) * viewLength) / 3; + return this._scrollTo(this._scrollDistance + scrollAmount); + } + + private scrollToToggleOptionValue() { + if (this.buttonToggleGroup && this.buttonToggleGroup.selected) { + const selectedToggleButton = this.buttonToggleGroup.selected as MatButtonToggle; + const viewLength = this.toggleGroupContainer.nativeElement.offsetWidth; + const {offsetLeft, offsetWidth} = (selectedToggleButton._buttonElement.nativeElement.offsetParent as HTMLElement); + const labelBeforePos = offsetLeft; // this.toggleGroup.nativeElement.offsetWidth - offsetLeft; + const labelAfterPos = labelBeforePos + offsetWidth; + const beforeVisiblePos = this.scrollDistance; + const afterVisiblePos = this.scrollDistance + viewLength; + if (labelBeforePos < beforeVisiblePos) { + this.scrollDistance -= beforeVisiblePos - labelBeforePos; + } else if (labelAfterPos > afterVisiblePos) { + this.scrollDistance += Math.min( + labelAfterPos - afterVisiblePos, + labelBeforePos - beforeVisiblePos, + ); + } + } + } + + private updatePagination() { + this.checkPaginationEnabled(); + this.checkPaginationControls(); + this.updateToggleHeaderScrollPosition(); + } + + private checkPaginationEnabled() { + if (this.toggleGroupContainer) { + const isEnabled = this.toggleGroup.nativeElement.scrollWidth > this.toggleGroupContainer.nativeElement.offsetWidth; + if (isEnabled !== this.showPaginationControls) { + if (!isEnabled) { + this.scrollDistance = 0; + } else { + setTimeout(() => { + this.scrollToToggleOptionValue(); + }, 0); + } + this.cd.markForCheck(); + this.showPaginationControls = isEnabled; + } + } else { + this.showPaginationControls = false; + } + } + + private checkPaginationControls() { + if (!this.showPaginationControls) { + this.leftPaginationEnabled = this.rightPaginationEnabled = false; + } else { + // Check if the pagination arrows should be activated. + this.leftPaginationEnabled = this.scrollDistance > 0; + this.rightPaginationEnabled = this.scrollDistance < this.getMaxScrollDistance(); + this.cd.markForCheck(); + } + } + + private getMaxScrollDistance(): number { + const lengthOfToggleGroup = this.toggleGroup.nativeElement.scrollWidth; + const viewLength = this.toggleGroupContainer.nativeElement.offsetWidth; + return lengthOfToggleGroup - viewLength || 0; + } + + private _scrollTo(position: number) { + if (!this.showPaginationControls) { + return {maxScrollDistance: 0, distance: 0}; + } else { + const maxScrollDistance = this.getMaxScrollDistance(); + this._scrollDistance = Math.max(0, Math.min(maxScrollDistance, position)); + this._scrollDistanceChanged = true; + this.checkPaginationControls(); + return {maxScrollDistance, distance: this._scrollDistance}; + } + } + + private updateToggleHeaderScrollPosition() { + const scrollDistance = this.scrollDistance; + const translateX = -scrollDistance; + this.toggleGroup.nativeElement.style.transform = `translateX(${Math.round(translateX)}px)`; + if (this.platform.TRIDENT || this.platform.EDGE) { + this.toggleGroupContainer.nativeElement.scrollLeft = 0; + } + } } diff --git a/ui-ngx/src/app/shared/components/toggle-select.component.html b/ui-ngx/src/app/shared/components/toggle-select.component.html index a5ce7778b5..819dc76ee4 100644 --- a/ui-ngx/src/app/shared/components/toggle-select.component.html +++ b/ui-ngx/src/app/shared/components/toggle-select.component.html @@ -20,6 +20,7 @@ useSelectOnMdLg="false" [disabled]="disabled" [appearance]="appearance" + [disablePagination]="disablePagination" [options]="options" [value]="modelValue" (valueChange)="updateModel($event)"> diff --git a/ui-ngx/src/app/shared/components/toggle-select.component.ts b/ui-ngx/src/app/shared/components/toggle-select.component.ts index 8338e04f6a..3eee0cf841 100644 --- a/ui-ngx/src/app/shared/components/toggle-select.component.ts +++ b/ui-ngx/src/app/shared/components/toggle-select.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input } from '@angular/core'; +import { Component, forwardRef, HostBinding, Input } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @@ -35,6 +35,9 @@ import { coerceBoolean } from '@shared/decorators/coercion'; }) export class ToggleSelectComponent extends _ToggleBase implements ControlValueAccessor { + @HostBinding('style.maxWidth') + get maxWidth() { return '100%'; } + @Input() @coerceBoolean() disabled: boolean; @@ -42,6 +45,10 @@ export class ToggleSelectComponent extends _ToggleBase implements ControlValueAc @Input() appearance: ToggleHeaderAppearance = 'stroked'; + @Input() + @coerceBoolean() + disablePagination = false; + modelValue: any; private propagateChange = null; From bc43a39643448132ec65f0a35a875a269ea85900 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 31 Jul 2023 16:05:27 +0300 Subject: [PATCH 155/168] UI: Refactoring --- .../entity/entity-select.component.ts | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index 93452e4620..6e235a2dad 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -121,18 +121,12 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte } writeValue(value: EntityId | null): void { - if (value != null) { - this.modelValue = value; - this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: false}); - this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: false}); - } else { - this.modelValue = { - entityType: this.defaultEntityType, - id: null - }; - this.entitySelectFormGroup.get('entityType').patchValue(this.defaultEntityType, {emitEvent: false}); - this.entitySelectFormGroup.get('entityId').patchValue(null, {emitEvent: false}); - } + this.modelValue = { + entityType: value?.entityType ? value.entityType : this.defaultEntityType, + id: value?.id ? value.id : null + }; + this.entitySelectFormGroup.get('entityType').patchValue(this.modelValue.entityType, {emitEvent: false}); + this.entitySelectFormGroup.get('entityId').patchValue(this.modelValue.id, {emitEvent: false}); } updateView(entityType: EntityType | AliasEntityType | null, entityId: string | null) { @@ -146,6 +140,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte || this.modelValue.entityType === AliasEntityType.CURRENT_USER || this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER) { this.modelValue.id = NULL_UUID; + } else if (this.modelValue.entityType === AliasEntityType.CURRENT_CUSTOMER && !this.modelValue.id) { + this.modelValue.id = NULL_UUID; } if (this.modelValue.entityType && this.modelValue.id) { From 3f963107da0454183e89dda9dc868b44ba6ea433 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 31 Jul 2023 16:34:03 +0300 Subject: [PATCH 156/168] UI: Refactoring --- .../components/entity/entity-select.component.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index 6e235a2dad..5190f486ad 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -121,10 +121,17 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte } writeValue(value: EntityId | null): void { - this.modelValue = { - entityType: value?.entityType ? value.entityType : this.defaultEntityType, - id: value?.id ? value.id : null - }; + if (value != null) { + this.modelValue = { + entityType: value.entityType, + id: value.id !== NULL_UUID ? value.id : null + }; + } else { + this.modelValue = { + entityType: value?.entityType ? value.entityType : this.defaultEntityType, + id: null + }; + } this.entitySelectFormGroup.get('entityType').patchValue(this.modelValue.entityType, {emitEvent: false}); this.entitySelectFormGroup.get('entityId').patchValue(this.modelValue.id, {emitEvent: false}); } From 6cee9caad73e7ce1d26b6887d70f9d54616ab1a5 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 31 Jul 2023 17:01:41 +0300 Subject: [PATCH 157/168] UI: Refactoring --- .../src/app/shared/components/entity/entity-select.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index 5190f486ad..4874c6c186 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -128,7 +128,7 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte }; } else { this.modelValue = { - entityType: value?.entityType ? value.entityType : this.defaultEntityType, + entityType: this.defaultEntityType, id: null }; } From 698dfba952ecf9430bd5a43dc104f851b37001d6 Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 1 Aug 2023 14:56:50 +0300 Subject: [PATCH 158/168] tbel: rollback validation switch --- .../script/api/tbel/DefaultTbelInvokeService.java | 7 ------- pom.xml | 2 +- ui-ngx/src/app/shared/models/ace/tbel/worker-tbel.js | 9 ++------- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java index bbf441a659..2a60980f84 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java @@ -66,8 +66,6 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem protected final Map scriptIdToHash = new ConcurrentHashMap<>(); protected final Map scriptMap = new ConcurrentHashMap<>(); - private final String tbelSwitch = "switch"; - private final String tbelSwitchErrorMsg = "TBEL does not support the 'switch'."; protected Cache compiledScriptsCache; private SandboxedParserConfiguration parserConfig; @@ -183,11 +181,6 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem lock.unlock(); } return scriptId; - } catch (CompileException ce) { - if ( ce.getExpr() != null && new String(ce.getExpr()).contains(tbelSwitch)) { - ce = new CompileException(tbelSwitchErrorMsg, ce.getExpr(), ce.getCursor(), ce.getCause()); - } - throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, scriptBody, ce); } catch (Exception e) { throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, scriptBody, e); } diff --git a/pom.xml b/pom.xml index 59d1eff5d3..df8c6ed8d6 100755 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,7 @@ 3.8.1 3.21.9 1.42.1 - 1.0.6 + 1.0.7 1.18.18 1.2.4 1.2.5 diff --git a/ui-ngx/src/app/shared/models/ace/tbel/worker-tbel.js b/ui-ngx/src/app/shared/models/ace/tbel/worker-tbel.js index 3a4b3d90b8..d1d47d0c75 100644 --- a/ui-ngx/src/app/shared/models/ace/tbel/worker-tbel.js +++ b/ui-ngx/src/app/shared/models/ace/tbel/worker-tbel.js @@ -5229,10 +5229,6 @@ var JSHINT = (function() { var a = [], p; while (!state.tokens.next.reach && state.tokens.next.id !== "(end)") { - if (state.tokens.next.value === "switch") { - warning("E067", state.tokens.next, "switch"); - break; - } if (state.tokens.next.id === ";") { p = peek(); @@ -9219,7 +9215,7 @@ var JSHINT = (function() { statements(0); } - if (state.tokens.next.id !== "(end)"&& state.tokens.next.value !== "switch") { + if (state.tokens.next.id !== "(end)") { quit("E041", state.tokens.curr); } @@ -11270,8 +11266,7 @@ var errors = { E064: "Super call may only be used within class method bodies.", E065: "Functions defined outside of strict mode with non-simple parameter lists may not " + "enable strict mode.", - E066: "Asynchronous iteration is only available with for-of loops.", - E067: "Expected an 'if/else' and instead saw 'switch'. TBEL does not support the 'switch' statement." + E066: "Asynchronous iteration is only available with for-of loops." }; var warnings = { From bd24bb7335f4501a46ff016494a3f148cb3435e7 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 1 Aug 2023 19:58:59 +0300 Subject: [PATCH 159/168] Widgets UI config: Responsive layout improvements. --- .../add-widget-dialog.component.html | 18 +++-- .../add-widget-dialog.component.scss | 9 +++ .../dashboard-page.component.html | 25 +++++-- .../dashboard-page.component.ts | 2 +- .../dashboard-toolbar.component.scss | 37 ++++++++++ .../dashboard-page/edit-widget.component.html | 44 +++++++---- .../dashboard-page/edit-widget.component.scss | 2 +- .../components/details-panel.component.html | 2 +- .../components/details-panel.component.scss | 13 ++-- .../alarms-table-basic-config.component.html | 2 +- .../widget/config/basic/basic-config.scss | 6 ++ ...entities-table-basic-config.component.html | 2 +- .../simple-card-basic-config.component.html | 2 +- ...meseries-table-basic-config.component.html | 2 +- .../value-card-basic-config.component.html | 2 +- .../chart/flot-basic-config.component.html | 2 +- .../basic/common/data-key-row.component.html | 3 +- .../basic/common/data-key-row.component.scss | 30 +++++++- .../common/data-keys-panel.component.html | 3 +- .../common/data-keys-panel.component.scss | 40 ++++++++-- .../timewindow-config-panel.component.html | 11 +-- .../common/legend-config.component.html | 2 +- .../widget/lib/settings/widget-settings.scss | 3 + .../widget/widget-config.component.html | 22 +++--- .../widget/widget-config.component.scss | 35 +++++++-- .../components/time/timewindow.component.scss | 3 + .../components/time/timewindow.component.ts | 7 +- .../components/toggle-header.component.html | 8 +- .../components/toggle-header.component.scss | 3 - .../components/toggle-header.component.ts | 74 +++++++++++++++---- .../components/toggle-select.component.html | 1 + .../components/toggle-select.component.ts | 3 + ui-ngx/src/form.scss | 66 ++++++++++++----- ui-ngx/src/styles.scss | 29 +++++++- 34 files changed, 393 insertions(+), 120 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html index 7de7acf413..47f8634462 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html @@ -17,14 +17,16 @@ --> -

widget.add

- : {{data.widgetInfo.widgetName}} -
- - {{ 'widget.basic-mode' | translate }} - {{ 'widget.advanced-mode' | translate }} - -
+
+

{{'widget.add' | translate}}: {{data.widgetInfo.widgetName}}

+
+ + {{ 'widget.basic-mode' | translate }} + {{ 'widget.advanced-mode' | translate }} + +
+
+ + + + @@ -360,7 +371,7 @@ [isReadOnly]="true" (closeDetails)="onEditWidgetClosed()">
- + {{ 'widget.basic-mode' | translate }} {{ 'widget.advanced-mode' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index cead69ab46..3996c1abaf 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -191,7 +191,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC } get hideToolbar(): boolean { - return (this.hideToolbarValue || this.hideToolbarSetting()) && !this.isEdit; + return ((this.hideToolbarValue || this.hideToolbarSetting()) && !this.isEdit) || (this.isEditingWidget || this.isAddingWidget); } @Input() diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss index 0d9ade9bf6..e43132c761 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss @@ -126,6 +126,7 @@ tb-dashboard-toolbar { @media #{$mat-lt-md} { height: $mobile-toolbar-height; max-height: $mobile-toolbar-height; + padding: 0 8px !important; } .close-action { @@ -150,8 +151,44 @@ tb-dashboard-toolbar { .tb-dashboard-action-panel { min-width: 0; height: $half-mobile-toolbar-height; + flex: 1 0 auto; + display: flex; + flex-direction: row-reverse; + place-content: center space-between; + align-items: center; + &.tb-left-panel { + flex: 1 1 auto; + } + + @media #{$mat-lt-md} { + padding-left: 12px; + } + + @media #{$mat-xs} { + gap: 3px; + padding-left: 0; + &.tb-left-panel { + padding-left: 12px; + } + } + + @media #{$mat-sm} { + gap: 6px; + } + + @media #{$mat-md} { + gap: 6px; + } + + @media #{$mat-gt-md} { + gap: 12px; + } @media #{$mat-gt-sm} { + place-content: center flex-start; + &.tb-left-panel { + place-content: center flex-end; + } height: 46px; } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html index a1c8ee6ade..86f069239c 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html @@ -32,26 +32,38 @@ chevron_left {{ 'action.back' | translate }} -
-
- - -
+
+ +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.scss index c31f1d5791..9c4e20a7a5 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.scss @@ -16,7 +16,7 @@ :host { .widget-preview-background { position: absolute; - top: 72px; + top: 68px; left: 0; right: 0; bottom: 0; diff --git a/ui-ngx/src/app/modules/home/components/details-panel.component.html b/ui-ngx/src/app/modules/home/components/details-panel.component.html index 15df7e99e5..748723197f 100644 --- a/ui-ngx/src/app/modules/home/components/details-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/details-panel.component.html @@ -21,7 +21,7 @@
- {{ headerTitle }} + {{ headerTitle }}
{{ headerSubtitle }} diff --git a/ui-ngx/src/app/modules/home/components/details-panel.component.scss b/ui-ngx/src/app/modules/home/components/details-panel.component.scss index 9002246841..451795a2d1 100644 --- a/ui-ngx/src/app/modules/home/components/details-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/details-panel.component.scss @@ -32,16 +32,14 @@ max-height: 120px; &.tb-details-title-header { min-width: 0; + padding: 0 16px 0 8px; } } .tb-details-title { width: inherit; margin: 20px 8px 0 0; - overflow: hidden; font-size: 1rem; font-weight: 400; - text-overflow: ellipsis; - white-space: nowrap; @media #{$mat-gt-sm} { font-size: 1.5rem; @@ -49,13 +47,16 @@ } .tb-details-subtitle { - width: inherit; margin: 10px 0; - overflow: hidden; font-size: 1rem; + opacity: .8; + } + + .tb-details-title-text, .tb-details-subtitle { + width: inherit; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - opacity: .8; } tb-dashboard { diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html index 18cd34609e..ffddc9df59 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html @@ -72,7 +72,7 @@
-
+
widget-config.show-card-buttons
{{ 'action.search' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-config.scss b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-config.scss index d29594fce3..0f88b8f1dc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-config.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-config.scss @@ -13,8 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +@import '../../../../../../../scss/constants'; + :host { display: flex; flex-direction: column; gap: 16px; + @media #{$mat-xs} { + gap: 8px; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html index c5501ae830..c68caab86e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html @@ -61,7 +61,7 @@
-
+
widget-config.show-card-buttons
{{ 'action.search' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.html index 142f32cd4e..92bd2e44ab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.html @@ -61,7 +61,7 @@
-
+
widget-config.show-card-buttons
{{ 'fullscreen.fullscreen' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html index bab6437485..49196fac3a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html @@ -61,7 +61,7 @@
-
+
widget-config.show-card-buttons
{{ 'action.search' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html index 51bb854826..788c997740 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html @@ -105,7 +105,7 @@
-
+
widget-config.show-card-buttons
{{ 'fullscreen.fullscreen' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html index 1439f74931..952b3f9031 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html @@ -61,7 +61,7 @@
-
+
widget-config.show-card-buttons
{{ 'fullscreen.fullscreen' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html index 22c4c2aace..62f34f3d28 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html @@ -158,7 +158,8 @@
-
-
+
legend.show-values
{{ 'legend.min-option' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.scss index 1971b02b6c..ed74372105 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.scss @@ -19,6 +19,9 @@ display: flex; flex-direction: column; gap: 16px; + @media #{$mat-xs} { + gap: 8px; + } .tb-widget-settings { .fields-group { diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html index 07f655c768..c9dd0732be 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html @@ -20,8 +20,10 @@ - - +
+ + +
@@ -48,18 +50,18 @@
-
+
{{ 'widget-config.display-icon' | translate }}
+ + + - - - @@ -247,7 +249,7 @@
widget-config.limits
-
+
widget-config.data-page-size
@@ -258,19 +260,19 @@
widget-config.data-settings
-
+
widget-config.units
-
+
widget-config.decimals
-
+
widget-config.no-data-display-message
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss index 91b3368ad0..701bfec099 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss @@ -20,16 +20,36 @@ .tb-widget-config { display: flex; flex-direction: column; - gap: 16px; + gap: 8px; .tb-widget-config-header { - padding: 24px 24px 8px; - height: 56px; + padding: 24px 24px 0; display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; + gap: 12px; + flex-direction: column-reverse; + align-items: flex-start; + @media #{$mat-gt-sm} { + gap: 0; + flex-direction: row; + align-items: center; + justify-content: space-between; + } + .tb-widget-config-header-components { + width: 100%; + flex: 1; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + } } .tb-widget-config-content { + & > .mat-content { + padding-top: 8px; + @media #{$mat-xs} { + padding-left: 8px; + padding-right: 8px; + } + } flex: 1; overflow: auto; & > div { @@ -39,6 +59,9 @@ display: flex; flex-direction: column; gap: 16px; + @media #{$mat-xs} { + gap: 8px; + } } } .tb-basic-mode-directive-error { 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 695362197d..af3feec6eb 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.scss @@ -17,6 +17,9 @@ min-width: 48px; margin: 8px 0; max-width: 100%; + &.no-margin { + margin: 0; + } .mdc-button { max-width: 100%; } diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.ts b/ui-ngx/src/app/shared/components/time/timewindow.component.ts index f9b40daac1..ff39e54fc7 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.ts @@ -18,7 +18,7 @@ import { ChangeDetectorRef, Component, ElementRef, - forwardRef, + forwardRef, HostBinding, Injector, Input, StaticProvider, @@ -83,6 +83,11 @@ export class TimewindowComponent implements ControlValueAccessor { return this.historyOnlyValue; } + @HostBinding('class.no-margin') + @Input() + @coerceBoolean() + noMargin = false; + @Input() @coerceBoolean() forAllTimeEnabled = false; diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.html b/ui-ngx/src/app/shared/components/toggle-header.component.html index c2136558e3..7aa391b3b4 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.html +++ b/ui-ngx/src/app/shared/components/toggle-header.component.html @@ -16,13 +16,14 @@ --> -
+
+ class="tb-toggle-header-pagination-button" [class]="{'tb-mat-32': !isMdLg, 'tb-mat-24': isMdLg}"> chevron_right diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.scss b/ui-ngx/src/app/shared/components/toggle-header.component.scss index dd983f3de9..7a41032961 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.scss +++ b/ui-ngx/src/app/shared/components/toggle-header.component.scss @@ -178,9 +178,6 @@ line-height: 16px; letter-spacing: 0.25px; } - .mat-mdc-select-value { - color: rgba(0, 0, 0, 0.38); - } .mat-mdc-select-arrow-wrapper { height: 12px; padding-left: 6px; diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.ts b/ui-ngx/src/app/shared/components/toggle-header.component.ts index 6599a6fe35..a7bddfc7b4 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.ts +++ b/ui-ngx/src/app/shared/components/toggle-header.component.ts @@ -35,7 +35,7 @@ import { import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { Subject, Subscription } from 'rxjs'; +import { BehaviorSubject, Subject, Subscription } from 'rxjs'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { coerceBoolean } from '@shared/decorators/coercion'; @@ -159,9 +159,20 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV @coerceBoolean() disablePagination = false; + @Input() + selectMediaBreakpoint = 'md-lg'; + @Input() @coerceBoolean() - useSelectOnMdLg = true; + set useSelectOnMdLg(value: boolean) { + if (value) { + this.selectMediaBreakpoint = 'md-lg'; + } else { + if (this.selectMediaBreakpoint === 'md-lg') { + this.selectMediaBreakpoint = ''; + } + } + } @Input() @coerceBoolean() @@ -174,7 +185,14 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV @coerceBoolean() disabled = false; - isMdLg: boolean; + get isMdLg(): boolean { + return !this.ignoreMdLgSize && this.isMdLgValue; + } + + private isMdLgValue: boolean; + private useSelectSubject = new BehaviorSubject(false); + + useSelect$ = this.useSelectSubject.asObservable(); private observeBreakpointSubscription: Subscription; @@ -186,11 +204,19 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV } ngOnInit() { - this.isMdLg = this.breakpointObserver.isMatched(MediaBreakpoints['md-lg']); + const mediaBreakpoints = [MediaBreakpoints['md-lg']]; + if (this.selectMediaBreakpoint && this.selectMediaBreakpoint !== 'md-lg') { + mediaBreakpoints.push(MediaBreakpoints[this.selectMediaBreakpoint]); + } this.observeBreakpointSubscription = this.breakpointObserver - .observe(MediaBreakpoints['md-lg']) + .observe(mediaBreakpoints) .subscribe((state: BreakpointState) => { - this.isMdLg = state.matches; + this.isMdLgValue = state.breakpoints[MediaBreakpoints['md-lg']]; + if (this.selectMediaBreakpoint) { + this.useSelectSubject.next(state.breakpoints[MediaBreakpoints[this.selectMediaBreakpoint]]); + } else { + this.useSelectSubject.next(false); + } this.cd.markForCheck(); } ); @@ -202,18 +228,21 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV } ngOnDestroy() { - if (this.toggleGroupResize$) { - this.toggleGroupResize$.disconnect(); - } + this.stopObservePagination(); super.ngOnDestroy(); } ngAfterViewInit() { - if (!this.disablePagination && !this.useSelectOnMdLg) { - this.toggleGroupResize$ = new ResizeObserver(() => { - this.updatePagination(); + if (!this.disablePagination) { + this.useSelect$.pipe(takeUntil(this._destroyed)).subscribe((useSelect) => { + if (useSelect) { + this.removePagination(); + } else { + setTimeout(() => { + this.startObservePagination(); + }, 0); + } }); - this.toggleGroupResize$.observe(this.toggleGroupContainer.nativeElement); } } @@ -243,6 +272,25 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV } } + private startObservePagination() { + this.toggleGroupResize$ = new ResizeObserver(() => { + this.updatePagination(); + }); + this.toggleGroupResize$.observe(this.toggleGroupContainer.nativeElement); + } + + private removePagination() { + this.stopObservePagination(); + this.showPaginationControls = false; + } + + private stopObservePagination() { + if (this.toggleGroupResize$) { + this.toggleGroupResize$.disconnect(); + this.toggleGroupResize$ = null; + } + } + private scrollHeader(direction: ScrollDirection) { const viewLength = this.toggleGroup.nativeElement.offsetWidth; // Move the scroll distance one-third the length of the tab list's viewport. diff --git a/ui-ngx/src/app/shared/components/toggle-select.component.html b/ui-ngx/src/app/shared/components/toggle-select.component.html index 819dc76ee4..c03e0cfcbf 100644 --- a/ui-ngx/src/app/shared/components/toggle-select.component.html +++ b/ui-ngx/src/app/shared/components/toggle-select.component.html @@ -21,6 +21,7 @@ [disabled]="disabled" [appearance]="appearance" [disablePagination]="disablePagination" + [selectMediaBreakpoint]="selectMediaBreakpoint" [options]="options" [value]="modelValue" (valueChange)="updateModel($event)"> diff --git a/ui-ngx/src/app/shared/components/toggle-select.component.ts b/ui-ngx/src/app/shared/components/toggle-select.component.ts index 3eee0cf841..c1541d1128 100644 --- a/ui-ngx/src/app/shared/components/toggle-select.component.ts +++ b/ui-ngx/src/app/shared/components/toggle-select.component.ts @@ -42,6 +42,9 @@ export class ToggleSelectComponent extends _ToggleBase implements ControlValueAc @coerceBoolean() disabled: boolean; + @Input() + selectMediaBreakpoint; + @Input() appearance: ToggleHeaderAppearance = 'stroked'; diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 4a4c018549..92feca1e8e 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -16,6 +16,15 @@ @import './scss/constants'; +@mixin form-row-column($breakpoint) { + @media #{$breakpoint} { + flex-direction: column; + align-items: stretch; + gap: 12px; + padding: 12px 12px 12px 16px; + } +} + .tb-default, .tb-dark { .tb-form-panel { box-shadow: 0 0 10px 6px rgba(11, 17, 51, 0.04); @@ -27,6 +36,10 @@ color: rgba(0, 0, 0, 0.87); letter-spacing: 0.15px; position: relative; + @media #{$mat-xs} { + padding: 12px; + gap: 8px; + } &.no-padding-bottom { padding-bottom: 0; } @@ -52,7 +65,6 @@ > .mat-expansion-panel { padding: 16px; .mat-expansion-panel-header { - height: 32px; .mat-slide { margin: 0; } @@ -66,6 +78,7 @@ overflow: visible; } > .mat-expansion-panel-header { + height: fit-content; user-select: none; font-weight: 500; font-size: 16px; @@ -98,6 +111,10 @@ flex-direction: column; gap: 16px; padding: 16px 0 0 !important; + @media #{$mat-xs} { + padding: 12px 0 0 !important; + gap: 8px; + } } } .tb-json-object-panel, .tb-css-content-panel { @@ -139,6 +156,14 @@ padding: 7px 7px 7px 16px; border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 6px; + &.column { + &-xs { + @include form-row-column($mat-xs) + } + &-lt-md { + @include form-row-column($mat-lt-md) + } + } &.no-border { border: none; border-radius: 0; @@ -360,12 +385,14 @@ } .tb-form-table-row { - height: 38px; display: flex; flex-direction: row; - gap: 12px; - padding-left: 12px; - + gap: 8px; + padding-left: 8px; + @media #{$mat-gt-md} { + gap: 12px; + padding-left: 12px; + } &.tb-draggable { gap: 0; padding-left: 0; @@ -376,12 +403,7 @@ display: flex; flex-direction: row; button.mat-mdc-icon-button.mat-mdc-button-base { - padding: 7px; - width: 38px; - height: 38px; - .mat-icon { - color: rgba(0, 0, 0, 0.38); - } + color: rgba(0, 0, 0, 0.38); &.tb-hidden { visibility: hidden; } @@ -434,21 +456,18 @@ } } - button.mat-mdc-button-base.tb-box-button { + button.mat-mdc-button-base.tb-box-button, .tb-form-table-row-cell-buttons button.mat-mdc-icon-button.mat-mdc-button-base { width: 40px; min-width: 40px; height: 40px; - padding: 7px; + padding: 8px; + &.mat-mdc-outlined-button { + padding: 7px; + } .mat-mdc-button-touch-target { width: 40px; height: 40px; } - &:not(:disabled) { - color: rgba(0, 0, 0, 0.54); - } - &:disabled { - color: rgba(0, 0, 0, 0.12); - } > .mat-icon { width: 24px; height: 24px; @@ -456,4 +475,13 @@ margin: 0; } } + + button.mat-mdc-button-base.tb-box-button { + &:not(:disabled) { + color: rgba(0, 0, 0, 0.54); + } + &:disabled { + color: rgba(0, 0, 0, 0.12); + } + } } diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 75fc0845cf..ec6060823d 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -625,9 +625,36 @@ mat-label { color: white; } } - .mat-mdc-select-value, .mat-mdc-select-arrow { + .mat-mdc-select-value, .mat-mdc-select-arrow, .mat-mdc-select-arrow:after { color: white; } + .mat-mdc-text-field-wrapper { + &.mdc-text-field--outlined { + &:not(.mdc-text-field--focused):not(.mdc-text-field--disabled):not(.mdc-text-field--invalid) { + &:not(:hover) { + .mdc-notched-outline { + .mdc-notched-outline__leading, .mdc-notched-outline__trailing { + border-color: white; + } + } + } + &:hover { + .mdc-notched-outline { + .mdc-notched-outline__leading, .mdc-notched-outline__trailing { + border-color: rgba(255, 255, 255, 0.87); + } + } + } + } + &:not(.mdc-text-field--disabled).mdc-text-field--focused { + .mdc-notched-outline { + .mdc-notched-outline__leading, .mdc-notched-outline__trailing { + border-color: rgba(255, 255, 255, 0.67); + } + } + } + } + } } .mat-toolbar.mat-mdc-table-toolbar { From eaf8ef0f14013a86f19a6f1eda782889e657b728 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 3 Aug 2023 12:36:15 +0300 Subject: [PATCH 160/168] fix_bug: default VARCHAR length for notification_request.info = 1M (old value = 1K) --- application/src/main/data/upgrade/3.5.1/schema_update.sql | 2 ++ dao/src/main/resources/sql/schema-entities.sql | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/application/src/main/data/upgrade/3.5.1/schema_update.sql b/application/src/main/data/upgrade/3.5.1/schema_update.sql index 1655ecb978..281a7fcbfa 100644 --- a/application/src/main/data/upgrade/3.5.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.5.1/schema_update.sql @@ -122,3 +122,5 @@ ALTER TABLE resource UPDATE resource SET etag = encode(sha256(decode(resource.data, 'base64')),'hex') WHERE resource.data is not null; +ALTER TABLE notification_request ALTER COLUMN info SET DATA TYPE varchar(1000000); + diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index bfb2eed805..4675de4e9a 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -826,7 +826,7 @@ CREATE TABLE IF NOT EXISTS notification_request ( targets VARCHAR(10000) NOT NULL, template_id UUID, template VARCHAR(10000000), - info VARCHAR(1000), + info VARCHAR(1000000), additional_config VARCHAR(1000), originator_entity_id UUID, originator_entity_type VARCHAR(32), From 850a7d3338071dbe7e12474292ad9b16775cc399 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 3 Aug 2023 13:00:25 +0300 Subject: [PATCH 161/168] UI: Fixed resize alarm filter config component --- ui-ngx/src/app/core/services/utils.service.ts | 26 +++++++++++++++-- .../alarm/alarm-filter-config.component.html | 22 +++++++------- .../alarm/alarm-filter-config.component.scss | 29 +++++++------------ .../alarm/alarm-filter-config.component.ts | 7 +++++ .../lib/alarms-table-widget.component.ts | 15 +++++++--- 5 files changed, 63 insertions(+), 36 deletions(-) diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index a6065dfe62..e585b2a19a 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -17,7 +17,7 @@ // eslint-disable-next-line @typescript-eslint/triple-slash-reference /// -import { Inject, Injectable, NgZone } from '@angular/core'; +import { ElementRef, Inject, Injectable, NgZone } from '@angular/core'; import { WINDOW } from '@core/services/window.service'; import { ExceptionData } from '@app/shared/models/error.models'; import { @@ -45,7 +45,7 @@ import { alarmFields, alarmSeverityTranslations, alarmStatusTranslations } from import { materialColors } from '@app/shared/models/material.models'; import { WidgetInfo } from '@home/models/widget-component.models'; import jsonSchemaDefaults from 'json-schema-defaults'; -import { Observable } from 'rxjs'; +import { fromEvent, Observable, Subscription } from 'rxjs'; import { publishReplay, refCount } from 'rxjs/operators'; import { WidgetContext } from '@app/modules/home/models/widget-component.models'; import { @@ -57,6 +57,8 @@ import { import { EntityId } from '@shared/models/id/entity-id'; import { DatePipe } from '@angular/common'; import { entityTypeTranslations } from '@shared/models/entity-type.models'; +import { OverlayRef } from '@angular/cdk/overlay'; +import { ResizeObserver } from '@juggle/resize-observer'; const i18nRegExp = new RegExp(`{${i18nPrefix}:[^{}]+}`, 'g'); @@ -528,4 +530,24 @@ export class UtilsService { return base64toObj(b64Encoded); } + public updateOverlayMaxHeigth(overlay: OverlayRef, observeElementRef?: ElementRef): Subscription { + const observeElement = observeElementRef ? observeElementRef.nativeElement : overlay.overlayElement.children[0]; + + const setMaxHeigthOverlay = () => { + const top = observeElement.getBoundingClientRect().top; + const viewport = window.innerHeight; + overlay.updateSize({ + maxHeight: viewport - top + }); + }; + + const observer = new ResizeObserver(() => { + setMaxHeigthOverlay(); + observer.unobserve(observeElement); + }); + observer.observe(observeElement); + + return fromEvent(window, 'resize').subscribe(() => setMaxHeigthOverlay()); + } + } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html index cbbcc2ccb9..3ccfc6ae9c 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html @@ -55,29 +55,28 @@ -
-
-
alarm.alarm-status-list
+
+
+
alarm.alarm-status-list
{{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }}
-
-
alarm.alarm-severity-list
+
+
alarm.alarm-severity-list
{{ alarmSeverityTranslationMap.get(alarmSeverityEnum[alarmSeverity]) | translate }}
-
-
alarm.alarm-type-list
+
+
alarm.alarm-type-list
+ class="tb-chips"> @@ -92,10 +91,9 @@
-
-
alarm.assignee
+
+
alarm.assignee
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss index 95f78f8fde..edac6ddef1 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss @@ -14,25 +14,11 @@ * limitations under the License. */ :host { - display: block; - overflow: scroll; + display: flex; max-width: 100%; .mdc-button { max-width: 100%; } - - .filters-row-mobile { - flex-direction: column; - align-items: start; - border: none; - padding: 0; - } - .filters-title-mobile { - font-size: 14px; - } - .filters-fields-width-mobile { - width: 100%; - } } :host ::ng-deep { @@ -45,9 +31,16 @@ text-overflow: ellipsis; } } - .mat-mdc-chip { - .mdc-evolution-chip__cell, .mat-mdc-chip-action, .mat-mdc-chip-action-label { - overflow: hidden; +} + +::ng-deep { + .tb-alarm-filter-config-component { + flex: 1; + + .mat-mdc-chip { + .mdc-evolution-chip__cell, .mat-mdc-chip-action, .mat-mdc-chip-action-label { + overflow: hidden; + } } } } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts index 46402923a9..adc741fcdd 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts @@ -44,6 +44,8 @@ import { MatChipInputEvent } from '@angular/material/chips'; import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; import { TranslateService } from '@ngx-translate/core'; import { deepClone } from '@core/utils'; +import { Subscription } from 'rxjs'; +import { UtilsService } from '@core/services/utils.service'; export const ALARM_FILTER_CONFIG_DATA = new InjectionToken('AlarmFilterConfigData'); @@ -114,6 +116,7 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal panelResult: AlarmFilterConfig = null; private alarmFilterConfig: AlarmFilterConfig; + private resizeWindows: Subscription; private propagateChange = (_: any) => {}; @@ -125,6 +128,7 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal private translate: TranslateService, private overlay: Overlay, private nativeElement: ElementRef, + private utils: UtilsService, private viewContainerRef: ViewContainerRef) { } @@ -213,6 +217,7 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal }); this.alarmFilterOverlayRef.attach(new TemplatePortal(this.alarmFilterPanel, this.viewContainerRef)); + this.resizeWindows = this.utils.updateOverlayMaxHeigth(this.alarmFilterOverlayRef); } cancel() { @@ -220,6 +225,7 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal if (this.overlayRef) { this.overlayRef.dispose(); } else { + this.resizeWindows.unsubscribe(); this.alarmFilterOverlayRef.dispose(); } } @@ -233,6 +239,7 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal if (this.overlayRef) { this.overlayRef.dispose(); } else { + this.resizeWindows.unsubscribe(); this.alarmFilterOverlayRef.dispose(); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index 8c08d900a3..f799ed43ff 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -585,10 +585,13 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, $event.stopPropagation(); } const target = $event.target || $event.srcElement || $event.currentTarget; - const config = new OverlayConfig(); - config.backdropClass = 'cdk-overlay-transparent-backdrop'; - config.panelClass = 'tb-filter-panel'; - config.hasBackdrop = true; + const config = new OverlayConfig({ + panelClass: 'tb-filter-panel', + backdropClass: 'cdk-overlay-transparent-backdrop', + hasBackdrop: true, + height: 'fit-content', + maxHeight: '75vh' + }); const connectedPosition: ConnectedPosition = { originX: 'end', originY: 'bottom', @@ -630,7 +633,11 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); const componentRef = overlayRef.attach(new ComponentPortal(AlarmFilterConfigComponent, this.viewContainerRef, injector)); + + const resizeWindows = this.utils.updateOverlayMaxHeigth(overlayRef, componentRef.location); + componentRef.onDestroy(() => { + resizeWindows.unsubscribe(); if (componentRef.instance.panelResult) { const result = componentRef.instance.panelResult; const alarmFilter = this.entityService.resolveAlarmFilter(result, false); From fedeab23e8f1fc423f5cbbfc284e426dad000a17 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 3 Aug 2023 15:52:45 +0300 Subject: [PATCH 162/168] renamed FetchTo enum to TbMsgSource and move it to util package to reuse as fetchFrom property in other nodes --- ...AbstractRuleEngineFlowIntegrationTest.java | 10 +-- ...actRuleEngineLifecycleIntegrationTest.java | 4 +- .../engine/geo/TbGpsGeofencingFilterNode.java | 6 ++ .../TbAbstractFetchToNodeConfiguration.java | 3 +- .../metadata/TbAbstractGetAttributesNode.java | 7 +- .../metadata/TbAbstractGetEntityDataNode.java | 5 +- .../TbAbstractGetEntityDetailsNode.java | 7 +- .../metadata/TbAbstractGetMappedDataNode.java | 5 +- .../metadata/TbAbstractNodeWithFetchTo.java | 9 ++- .../TbFetchDeviceCredentialsNode.java | 11 +-- ...tchDeviceCredentialsNodeConfiguration.java | 3 +- .../engine/metadata/TbGetAttributesNode.java | 5 +- .../TbGetAttributesNodeConfiguration.java | 3 +- .../metadata/TbGetCustomerDetailsNode.java | 5 +- ...TbGetCustomerDetailsNodeConfiguration.java | 3 +- .../engine/metadata/TbGetDeviceAttrNode.java | 5 +- .../TbGetDeviceAttrNodeConfiguration.java | 3 +- .../TbGetEntityDataNodeConfiguration.java | 3 +- .../TbGetOriginatorFieldsConfiguration.java | 3 +- .../metadata/TbGetOriginatorFieldsNode.java | 5 +- .../TbGetRelatedDataNodeConfiguration.java | 3 +- .../metadata/TbGetTenantDetailsNode.java | 5 +- .../TbGetTenantDetailsNodeConfiguration.java | 3 +- .../FetchTo.java => util/TbMsgSource.java} | 4 +- .../TbFetchDeviceCredentialsNodeTest.java | 5 +- .../metadata/TbGetAttributesNodeTest.java | 77 ++++++++++--------- .../TbGetCustomerAttributeNodeTest.java | 19 ++--- .../TbGetCustomerDetailsNodeTest.java | 29 +++---- .../TbGetOriginatorFieldsNodeTest.java | 33 ++++---- .../TbGetRelatedAttributeNodeTest.java | 25 +++--- .../TbGetTenantAttributeNodeTest.java | 19 ++--- .../metadata/TbGetTenantDetailsNodeTest.java | 23 +++--- 32 files changed, 192 insertions(+), 158 deletions(-) rename rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/{metadata/FetchTo.java => util/TbMsgSource.java} (90%) diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java index e626adf5f4..b86287c5d7 100644 --- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java @@ -27,7 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; -import org.thingsboard.rule.engine.metadata.FetchTo; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.rule.engine.metadata.TbGetAttributesNode; import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; import org.thingsboard.server.actors.ActorSystemContext; @@ -144,7 +144,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode1.setDebugMode(true); TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration(); - configuration1.setFetchTo(FetchTo.METADATA); + configuration1.setFetchTo(TbMsgSource.METADATA); configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1)); @@ -154,7 +154,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode2.setDebugMode(true); TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); - configuration2.setFetchTo(FetchTo.METADATA); + configuration2.setFetchTo(TbMsgSource.METADATA); configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2)); @@ -250,7 +250,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode1.setDebugMode(true); TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration(); - configuration1.setFetchTo(FetchTo.METADATA); + configuration1.setFetchTo(TbMsgSource.METADATA); configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1)); @@ -284,7 +284,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode2.setDebugMode(true); TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); - configuration2.setFetchTo(FetchTo.METADATA); + configuration2.setFetchTo(TbMsgSource.METADATA); configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2)); diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java index 6216f993dc..9b05be4a05 100644 --- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java @@ -24,7 +24,7 @@ import org.junit.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.metadata.FetchTo; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.rule.engine.metadata.TbGetAttributesNode; import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; import org.thingsboard.server.actors.ActorSystemContext; @@ -98,7 +98,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac ruleNode.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); ruleNode.setDebugMode(true); TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration(); - configuration.setFetchTo(FetchTo.METADATA); + configuration.setFetchTo(TbMsgSource.METADATA); configuration.setServerAttributeNames(Collections.singletonList("serverAttributeKey")); ruleNode.setConfiguration(JacksonUtil.valueToTree(configuration)); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java index 20e7039be4..a9276bbb31 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.geo; 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.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -64,6 +65,11 @@ import org.thingsboard.server.common.msg.TbMsg; configDirective = "tbFilterNodeGpsGeofencingConfig") public class TbGpsGeofencingFilterNode extends AbstractGeofencingNode { + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + super.init(ctx, configuration); + } + @Override public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { ctx.tellNext(msg, checkMatches(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractFetchToNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractFetchToNodeConfiguration.java index 14c27a7ac7..2c14d1bea9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractFetchToNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractFetchToNodeConfiguration.java @@ -16,10 +16,11 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; +import org.thingsboard.rule.engine.util.TbMsgSource; @Data public abstract class TbAbstractFetchToNodeConfiguration { - private FetchTo fetchTo; + private TbMsgSource fetchTo; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java index 6b148f2dd4..3ec1fd4921 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java @@ -28,6 +28,7 @@ import org.thingsboard.rule.engine.api.TbContext; 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.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; @@ -67,7 +68,7 @@ public abstract class TbAbstractGetAttributesNode safePutAttributes(ctx, msg, msgDataAsObjectNode, entityId), @@ -84,7 +85,7 @@ public abstract class TbAbstractGetAttributesNode(true, newConfigObjectNode); } return upgradeConfigurationToUseFetchTo(oldProperty, ifTrue, ifFalse, newConfigObjectNode); @@ -165,7 +166,7 @@ public abstract class TbAbstractGetAttributesNode extends Tb @Override public void onMsg(TbContext ctx, TbMsg msg) { - var msgDataAsObjectNode = FetchTo.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null; + var msgDataAsObjectNode = TbMsgSource.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null; withCallback(findEntityAsync(ctx, msg.getOriginator()), entityId -> processDataAndTell(ctx, msg, entityId, msgDataAsObjectNode), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); @@ -89,7 +90,7 @@ public abstract class TbAbstractGetEntityDataNode extends Tb } else { throw new TbNodeException("property to update: '" + OLD_DATA_TO_FETCH_PROPERTY_NAME + "' has unexpected value: " + value + ". Allowed values: true or false!"); } - newConfigObjectNode.put(FETCH_TO_PROPERTY_NAME, FetchTo.METADATA.name()); + newConfigObjectNode.put(FETCH_TO_PROPERTY_NAME, TbMsgSource.METADATA.name()); return new TbPair<>(true, newConfigObjectNode); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java index 5707d64071..e9b0cecd1a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.util.ContactBasedEntityDetails; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.ContactBased; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.msg.TbMsg; @@ -37,7 +38,7 @@ public abstract class TbAbstractGetEntityDetailsNode ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); @@ -114,9 +115,9 @@ public abstract class TbAbstractGetEntityDetailsNode(false, oldConfiguration); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeConfiguration.java index 038e6c1c63..70712b4370 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeConfiguration.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; @Data @EqualsAndHashCode(callSuper = true) @@ -28,7 +29,7 @@ public class TbFetchDeviceCredentialsNodeConfiguration extends TbAbstractFetchTo @Override public TbFetchDeviceCredentialsNodeConfiguration defaultConfiguration() { var configuration = new TbFetchDeviceCredentialsNodeConfiguration(); - configuration.setFetchTo(FetchTo.METADATA); + configuration.setFetchTo(TbMsgSource.METADATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index edc7bdc44e..9fbe5eb666 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.TbContext; 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.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.util.TbPair; @@ -62,8 +63,8 @@ public class TbGetAttributesNode extends TbAbstractGetAttributesNode(false, oldConfiguration); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java index 2450796b59..fe15ff817f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; import java.util.Collections; import java.util.List; @@ -47,7 +48,7 @@ public class TbGetAttributesNodeConfiguration extends TbAbstractFetchToNodeConfi configuration.setLatestTsKeyNames(Collections.emptyList()); configuration.setTellFailureIfAbsent(true); configuration.setGetLatestValueWithTs(false); - configuration.setFetchTo(FetchTo.METADATA); + configuration.setFetchTo(TbMsgSource.METADATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java index 377e9eb0da..f0b391ca54 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.TbContext; 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.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasName; @@ -112,8 +113,8 @@ public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode(false, oldConfiguration); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeConfiguration.java index c49a1d3c30..370e7cd5a3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; import java.util.Collections; @@ -29,7 +30,7 @@ public class TbGetCustomerDetailsNodeConfiguration extends TbAbstractGetEntityDe public TbGetCustomerDetailsNodeConfiguration defaultConfiguration() { var configuration = new TbGetCustomerDetailsNodeConfiguration(); configuration.setDetailsList(Collections.emptyList()); - configuration.setFetchTo(FetchTo.DATA); + configuration.setFetchTo(TbMsgSource.DATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java index eb82d26007..ac88005f51 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java @@ -25,6 +25,7 @@ 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.rule.engine.util.EntitiesRelatedDeviceIdAsyncLoader; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.util.TbPair; @@ -66,8 +67,8 @@ public class TbGetDeviceAttrNode extends TbAbstractGetAttributesNode(false, oldConfiguration); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeConfiguration.java index 60a2c9cdf7..e6be83eb0c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.rule.engine.data.DeviceRelationsQuery; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @@ -38,7 +39,7 @@ public class TbGetDeviceAttrNodeConfiguration extends TbGetAttributesNodeConfigu configuration.setLatestTsKeyNames(Collections.emptyList()); configuration.setTellFailureIfAbsent(true); configuration.setGetLatestValueWithTs(false); - configuration.setFetchTo(FetchTo.METADATA); + configuration.setFetchTo(TbMsgSource.METADATA); var deviceRelationsQuery = new DeviceRelationsQuery(); deviceRelationsQuery.setDirection(EntitySearchDirection.FROM); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityDataNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityDataNodeConfiguration.java index eb3f3b805c..ec57cba3d2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityDataNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityDataNodeConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; import java.util.HashMap; @@ -34,7 +35,7 @@ public class TbGetEntityDataNodeConfiguration extends TbGetMappedDataNodeConfigu dataMapping.putIfAbsent("alarmThreshold", "threshold"); configuration.setDataMapping(dataMapping); configuration.setDataToFetch(DataToFetch.ATTRIBUTES); - configuration.setFetchTo(FetchTo.METADATA); + configuration.setFetchTo(TbMsgSource.METADATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsConfiguration.java index 45ca8e19c0..6c9f78e9b4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; import java.util.HashMap; import java.util.Map; @@ -37,7 +38,7 @@ public class TbGetOriginatorFieldsConfiguration extends TbGetMappedDataNodeConfi dataMapping.put("type", "originatorType"); configuration.setDataMapping(dataMapping); configuration.setIgnoreNullStrings(false); - configuration.setFetchTo(FetchTo.METADATA); + configuration.setFetchTo(TbMsgSource.METADATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java index 30c3e10cde..cb7877d99a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java @@ -23,6 +23,7 @@ import org.thingsboard.rule.engine.api.TbContext; 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.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.util.TbPair; @@ -58,7 +59,7 @@ public class TbGetOriginatorFieldsNode extends TbAbstractGetMappedDataNode(true, newConfigObjectNode); } return new TbPair<>(false, oldConfiguration); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDataNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDataNodeConfiguration.java index 4c78915ae4..4cb0723365 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDataNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDataNodeConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.rule.engine.data.RelationsQuery; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; @@ -38,7 +39,7 @@ public class TbGetRelatedDataNodeConfiguration extends TbGetEntityDataNodeConfig dataMapping.putIfAbsent("serialNumber", "sn"); configuration.setDataMapping(dataMapping); configuration.setDataToFetch(DataToFetch.ATTRIBUTES); - configuration.setFetchTo(FetchTo.METADATA); + configuration.setFetchTo(TbMsgSource.METADATA); var relationsQuery = new RelationsQuery(); var relationEntityTypeFilter = new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java index 2a5039f849..54d95645e1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java @@ -23,6 +23,7 @@ import org.thingsboard.rule.engine.api.TbContext; 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.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -67,8 +68,8 @@ public class TbGetTenantDetailsNode extends TbAbstractGetEntityDetailsNode(false, oldConfiguration); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeConfiguration.java index 35cdc42a54..f50f44ec23 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; import java.util.Collections; @@ -29,7 +30,7 @@ public class TbGetTenantDetailsNodeConfiguration extends TbAbstractGetEntityDeta public TbGetTenantDetailsNodeConfiguration defaultConfiguration() { var configuration = new TbGetTenantDetailsNodeConfiguration(); configuration.setDetailsList(Collections.emptyList()); - configuration.setFetchTo(FetchTo.DATA); + configuration.setFetchTo(TbMsgSource.DATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/FetchTo.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TbMsgSource.java similarity index 90% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/FetchTo.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TbMsgSource.java index 811868bef0..e73b7976fa 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/FetchTo.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TbMsgSource.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.metadata; +package org.thingsboard.rule.engine.util; -public enum FetchTo { +public enum TbMsgSource { DATA, METADATA diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java index 5de25ebfc2..c5044eab37 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java @@ -28,6 +28,7 @@ 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.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; @@ -83,13 +84,13 @@ public class TbFetchDeviceCredentialsNodeTest { @Test void givenDefaultConfig_whenInit_thenOK() { assertThat(node.config).isEqualTo(config); - assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); + assertThat(node.fetchTo).isEqualTo(TbMsgSource.METADATA); } @Test void givenDefaultConfig_whenVerify_thenOK() { var defaultConfig = new TbFetchDeviceCredentialsNodeConfiguration().defaultConfiguration(); - assertThat(defaultConfig.getFetchTo()).isEqualTo(FetchTo.METADATA); + assertThat(defaultConfig.getFetchTo()).isEqualTo(TbMsgSource.METADATA); } @Test diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java index 2263b272b9..6c8e83a22f 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java @@ -31,6 +31,7 @@ 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.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; @@ -126,7 +127,7 @@ public class TbGetAttributesNodeTest { @Test public void givenFetchAttributesToMetadata_whenOnMsg_thenShouldTellSuccess() throws Exception { // GIVEN - node = initNode(FetchTo.METADATA, false, false); + node = initNode(TbMsgSource.METADATA, false, false); var msg = getTbMsg(ORIGINATOR); // WHEN @@ -135,17 +136,17 @@ public class TbGetAttributesNodeTest { // THEN var resultMsg = checkMsg(true); - checkAttributes(resultMsg, FetchTo.METADATA, "cs_", clientAttributes); - checkAttributes(resultMsg, FetchTo.METADATA, "ss_", serverAttributes); - checkAttributes(resultMsg, FetchTo.METADATA, "shared_", sharedAttributes); + checkAttributes(resultMsg, TbMsgSource.METADATA, "cs_", clientAttributes); + checkAttributes(resultMsg, TbMsgSource.METADATA, "ss_", serverAttributes); + checkAttributes(resultMsg, TbMsgSource.METADATA, "shared_", sharedAttributes); - checkTs(resultMsg, FetchTo.METADATA, false, tsKeys); + checkTs(resultMsg, TbMsgSource.METADATA, false, tsKeys); } @Test public void givenFetchLatestTimeseriesToMetadata_whenOnMsg_thenShouldTellSuccess() throws Exception { // GIVEN - node = initNode(FetchTo.METADATA, true, false); + node = initNode(TbMsgSource.METADATA, true, false); var msg = getTbMsg(ORIGINATOR); // WHEN @@ -154,17 +155,17 @@ public class TbGetAttributesNodeTest { // THEN var resultMsg = checkMsg(true); - checkAttributes(resultMsg, FetchTo.METADATA, "cs_", clientAttributes); - checkAttributes(resultMsg, FetchTo.METADATA, "ss_", serverAttributes); - checkAttributes(resultMsg, FetchTo.METADATA, "shared_", sharedAttributes); + checkAttributes(resultMsg, TbMsgSource.METADATA, "cs_", clientAttributes); + checkAttributes(resultMsg, TbMsgSource.METADATA, "ss_", serverAttributes); + checkAttributes(resultMsg, TbMsgSource.METADATA, "shared_", sharedAttributes); - checkTs(resultMsg, FetchTo.METADATA, true, tsKeys); + checkTs(resultMsg, TbMsgSource.METADATA, true, tsKeys); } @Test public void givenFetchAttributesToData_whenOnMsg_thenShouldTellSuccess() throws Exception { // GIVEN - node = initNode(FetchTo.DATA, false, false); + node = initNode(TbMsgSource.DATA, false, false); var msg = getTbMsg(ORIGINATOR); // WHEN @@ -173,17 +174,17 @@ public class TbGetAttributesNodeTest { // THEN var resultMsg = checkMsg(true); - checkAttributes(resultMsg, FetchTo.DATA, "cs_", clientAttributes); - checkAttributes(resultMsg, FetchTo.DATA, "ss_", serverAttributes); - checkAttributes(resultMsg, FetchTo.DATA, "shared_", sharedAttributes); + checkAttributes(resultMsg, TbMsgSource.DATA, "cs_", clientAttributes); + checkAttributes(resultMsg, TbMsgSource.DATA, "ss_", serverAttributes); + checkAttributes(resultMsg, TbMsgSource.DATA, "shared_", sharedAttributes); - checkTs(resultMsg, FetchTo.DATA, false, tsKeys); + checkTs(resultMsg, TbMsgSource.DATA, false, tsKeys); } @Test public void givenFetchLatestTimeseriesToData_whenOnMsg_thenShouldTellSuccess() throws Exception { // GIVEN - node = initNode(FetchTo.DATA, true, false); + node = initNode(TbMsgSource.DATA, true, false); var msg = getTbMsg(ORIGINATOR); // WHEN @@ -192,17 +193,17 @@ public class TbGetAttributesNodeTest { // THEN var resultMsg = checkMsg(true); - checkAttributes(resultMsg, FetchTo.DATA, "cs_", clientAttributes); - checkAttributes(resultMsg, FetchTo.DATA, "ss_", serverAttributes); - checkAttributes(resultMsg, FetchTo.DATA, "shared_", sharedAttributes); + checkAttributes(resultMsg, TbMsgSource.DATA, "cs_", clientAttributes); + checkAttributes(resultMsg, TbMsgSource.DATA, "ss_", serverAttributes); + checkAttributes(resultMsg, TbMsgSource.DATA, "shared_", sharedAttributes); - checkTs(resultMsg, FetchTo.DATA, true, tsKeys); + checkTs(resultMsg, TbMsgSource.DATA, true, tsKeys); } @Test public void givenFetchAttributesToMetadata_whenOnMsg_thenShouldTellFailure() throws Exception { // GIVEN - node = initNode(FetchTo.METADATA, false, true); + node = initNode(TbMsgSource.METADATA, false, true); var msg = getTbMsg(ORIGINATOR); // WHEN @@ -211,17 +212,17 @@ public class TbGetAttributesNodeTest { // THEN var actualMsg = checkMsg(false); - checkAttributes(actualMsg, FetchTo.METADATA, "cs_", clientAttributes); - checkAttributes(actualMsg, FetchTo.METADATA, "ss_", serverAttributes); - checkAttributes(actualMsg, FetchTo.METADATA, "shared_", sharedAttributes); + checkAttributes(actualMsg, TbMsgSource.METADATA, "cs_", clientAttributes); + checkAttributes(actualMsg, TbMsgSource.METADATA, "ss_", serverAttributes); + checkAttributes(actualMsg, TbMsgSource.METADATA, "shared_", sharedAttributes); - checkTs(actualMsg, FetchTo.METADATA, false, tsKeys); + checkTs(actualMsg, TbMsgSource.METADATA, false, tsKeys); } @Test public void givenFetchLatestTimeseriesToData_whenOnMsg_thenShouldTellFailure() throws Exception { // GIVEN - node = initNode(FetchTo.DATA, true, true); + node = initNode(TbMsgSource.DATA, true, true); var msg = getTbMsg(ORIGINATOR); // WHEN @@ -230,17 +231,17 @@ public class TbGetAttributesNodeTest { // THEN var actualMsg = checkMsg(false); - checkAttributes(actualMsg, FetchTo.DATA, "cs_", clientAttributes); - checkAttributes(actualMsg, FetchTo.DATA, "ss_", serverAttributes); - checkAttributes(actualMsg, FetchTo.DATA, "shared_", sharedAttributes); + checkAttributes(actualMsg, TbMsgSource.DATA, "cs_", clientAttributes); + checkAttributes(actualMsg, TbMsgSource.DATA, "ss_", serverAttributes); + checkAttributes(actualMsg, TbMsgSource.DATA, "shared_", sharedAttributes); - checkTs(actualMsg, FetchTo.DATA, true, tsKeys); + checkTs(actualMsg, TbMsgSource.DATA, true, tsKeys); } @Test public void givenFetchLatestTimeseriesToDataAndDataIsNotJsonObject_whenOnMsg_thenException() throws Exception { // GIVEN - node = initNode(FetchTo.DATA, true, true); + node = initNode(TbMsgSource.DATA, true, true); var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN @@ -305,15 +306,15 @@ public class TbGetAttributesNodeTest { return resultMsg; } - private void checkAttributes(TbMsg actualMsg, FetchTo fetchTo, String prefix, List attributes) { + private void checkAttributes(TbMsg actualMsg, TbMsgSource fetchTo, String prefix, List attributes) { var msgData = JacksonUtil.toJsonNode(actualMsg.getData()); attributes.stream() .filter(attribute -> !attribute.equals("unknown")) .forEach(attribute -> { String result = null; - if (FetchTo.DATA.equals(fetchTo)) { + if (TbMsgSource.DATA.equals(fetchTo)) { result = msgData.get(prefix + attribute).asText(); - } else if (FetchTo.METADATA.equals(fetchTo)) { + } else if (TbMsgSource.METADATA.equals(fetchTo)) { result = actualMsg.getMetaData().getValue(prefix + attribute); } assertNotNull(result); @@ -321,7 +322,7 @@ public class TbGetAttributesNodeTest { }); } - private void checkTs(TbMsg actualMsg, FetchTo fetchTo, boolean getLatestValueWithTs, List tsKeys) { + private void checkTs(TbMsg actualMsg, TbMsgSource fetchTo, boolean getLatestValueWithTs, List tsKeys) { var msgData = JacksonUtil.toJsonNode(actualMsg.getData()); long value = 1L; for (var key : tsKeys) { @@ -335,9 +336,9 @@ public class TbGetAttributesNodeTest { } else { expectedValue = "{\"data\":" + value + "}"; } - if (FetchTo.DATA.equals(fetchTo)) { + if (TbMsgSource.DATA.equals(fetchTo)) { actualValue = JacksonUtil.toString(msgData.get(key)); - } else if (FetchTo.METADATA.equals(fetchTo)) { + } else if (TbMsgSource.METADATA.equals(fetchTo)) { actualValue = actualMsg.getMetaData().getValue(key); } assertNotNull(actualValue); @@ -346,7 +347,7 @@ public class TbGetAttributesNodeTest { } } - private TbGetAttributesNode initNode(FetchTo fetchTo, boolean getLatestValueWithTs, boolean isTellFailureIfAbsent) throws TbNodeException { + private TbGetAttributesNode initNode(TbMsgSource fetchTo, boolean getLatestValueWithTs, boolean isTellFailureIfAbsent) throws TbNodeException { var config = new TbGetAttributesNodeConfiguration(); config.setClientAttributeNames(List.of("client_attr_1", "client_attr_2", "${client_attr_metadata}", "unknown")); config.setServerAttributeNames(List.of("server_attr_1", "server_attr_2", "${server_attr_metadata}", "unknown")); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index b413d2c576..291614464a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -32,6 +32,7 @@ import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -161,7 +162,7 @@ public class TbGetCustomerAttributeNodeTest { assertThat(node.config).isEqualTo(config); assertThat(config.getDataMapping()).isEqualTo(Map.of("alarmThreshold", "threshold")); assertThat(config.getDataToFetch()).isEqualTo(DataToFetch.ATTRIBUTES); - assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); + assertThat(node.fetchTo).isEqualTo(TbMsgSource.METADATA); } @Test @@ -172,7 +173,7 @@ public class TbGetCustomerAttributeNodeTest { "sourceAttr2", "targetKey2", "sourceAttr3", "targetKey3")); config.setDataToFetch(DataToFetch.LATEST_TELEMETRY); - config.setFetchTo(FetchTo.DATA); + config.setFetchTo(TbMsgSource.DATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); // WHEN @@ -185,7 +186,7 @@ public class TbGetCustomerAttributeNodeTest { "sourceAttr2", "targetKey2", "sourceAttr3", "targetKey3")); assertThat(config.getDataToFetch()).isEqualTo(DataToFetch.LATEST_TELEMETRY); - assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); + assertThat(node.fetchTo).isEqualTo(TbMsgSource.DATA); } @Test @@ -207,7 +208,7 @@ public class TbGetCustomerAttributeNodeTest { @Test public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN - node.fetchTo = FetchTo.DATA; + node.fetchTo = TbMsgSource.DATA; msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN @@ -261,7 +262,7 @@ public class TbGetCustomerAttributeNodeTest { var device = new Device(new DeviceId(UUID.randomUUID())); device.setCustomerId(CUSTOMER_ID); - prepareMsgAndConfig(FetchTo.DATA, DataToFetch.ATTRIBUTES, device.getId()); + prepareMsgAndConfig(TbMsgSource.DATA, DataToFetch.ATTRIBUTES, device.getId()); List attributesList = List.of( new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L), @@ -308,7 +309,7 @@ public class TbGetCustomerAttributeNodeTest { var user = new User(new UserId(UUID.randomUUID())); user.setCustomerId(CUSTOMER_ID); - prepareMsgAndConfig(FetchTo.METADATA, DataToFetch.ATTRIBUTES, user.getId()); + prepareMsgAndConfig(TbMsgSource.METADATA, DataToFetch.ATTRIBUTES, user.getId()); List attributesList = List.of( new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L), @@ -354,7 +355,7 @@ public class TbGetCustomerAttributeNodeTest { // GIVEN var customer = new Customer(new CustomerId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.DATA, DataToFetch.LATEST_TELEMETRY, customer.getId()); + prepareMsgAndConfig(TbMsgSource.DATA, DataToFetch.LATEST_TELEMETRY, customer.getId()); List timeseriesList = List.of( new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")), @@ -398,7 +399,7 @@ public class TbGetCustomerAttributeNodeTest { var asset = new Asset(new AssetId(UUID.randomUUID())); asset.setCustomerId(new CustomerId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.METADATA, DataToFetch.LATEST_TELEMETRY, asset.getId()); + prepareMsgAndConfig(TbMsgSource.METADATA, DataToFetch.LATEST_TELEMETRY, asset.getId()); List timeseriesList = List.of( new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")), @@ -450,7 +451,7 @@ public class TbGetCustomerAttributeNodeTest { Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); } - private void prepareMsgAndConfig(FetchTo fetchTo, DataToFetch dataToFetch, EntityId originator) { + private void prepareMsgAndConfig(TbMsgSource fetchTo, DataToFetch dataToFetch, EntityId originator) { config.setDataMapping(Map.of( "sourceKey1", "targetKey1", "${metaDataPattern1}", "$[messageBodyPattern1]", diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index 1908578801..51625eafe7 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -31,6 +31,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.util.ContactBasedEntityDetails; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Device; @@ -133,14 +134,14 @@ public class TbGetCustomerDetailsNodeTest { @Test public void givenDefaultConfig_whenInit_thenOK() { assertThat(config.getDetailsList()).isEqualTo(Collections.emptyList()); - assertThat(config.getFetchTo()).isEqualTo(FetchTo.DATA); + assertThat(config.getFetchTo()).isEqualTo(TbMsgSource.DATA); } @Test public void givenCustomConfig_whenInit_thenOK() throws TbNodeException { // GIVEN config.setDetailsList(List.of(ContactBasedEntityDetails.ID, ContactBasedEntityDetails.PHONE)); - config.setFetchTo(FetchTo.METADATA); + config.setFetchTo(TbMsgSource.METADATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); // WHEN @@ -149,14 +150,14 @@ public class TbGetCustomerDetailsNodeTest { // THEN assertThat(node.config).isEqualTo(config); assertThat(config.getDetailsList()).isEqualTo(List.of(ContactBasedEntityDetails.ID, ContactBasedEntityDetails.PHONE)); - assertThat(config.getFetchTo()).isEqualTo(FetchTo.METADATA); - assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); + assertThat(config.getFetchTo()).isEqualTo(TbMsgSource.METADATA); + assertThat(node.fetchTo).isEqualTo(TbMsgSource.METADATA); } @Test public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN - node.fetchTo = FetchTo.DATA; + node.fetchTo = TbMsgSource.DATA; msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN @@ -174,7 +175,7 @@ public class TbGetCustomerDetailsNodeTest { device.setId(new DeviceId(UUID.randomUUID())); device.setCustomerId(customer.getId()); - prepareMsgAndConfig(FetchTo.DATA, List.of(ContactBasedEntityDetails.values()), device.getId()); + prepareMsgAndConfig(TbMsgSource.DATA, List.of(ContactBasedEntityDetails.values()), device.getId()); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(deviceServiceMock.findDeviceByIdAsync(eq(TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); @@ -216,7 +217,7 @@ public class TbGetCustomerDetailsNodeTest { asset.setId(new AssetId(UUID.randomUUID())); asset.setCustomerId(customer.getId()); - prepareMsgAndConfig(FetchTo.METADATA, List.of(ContactBasedEntityDetails.ID, ContactBasedEntityDetails.TITLE, ContactBasedEntityDetails.PHONE), asset.getId()); + prepareMsgAndConfig(TbMsgSource.METADATA, List.of(ContactBasedEntityDetails.ID, ContactBasedEntityDetails.TITLE, ContactBasedEntityDetails.PHONE), asset.getId()); when(ctxMock.getAssetService()).thenReturn(assetServiceMock); when(assetServiceMock.findAssetByIdAsync(eq(TENANT_ID), eq(asset.getId()))).thenReturn(Futures.immediateFuture(asset)); @@ -254,7 +255,7 @@ public class TbGetCustomerDetailsNodeTest { user.setId(new UserId(UUID.randomUUID())); user.setCustomerId(customer.getId()); - prepareMsgAndConfig(FetchTo.DATA, List.of(ContactBasedEntityDetails.ZIP, ContactBasedEntityDetails.ADDRESS, ContactBasedEntityDetails.ADDRESS2), user.getId()); + prepareMsgAndConfig(TbMsgSource.DATA, List.of(ContactBasedEntityDetails.ZIP, ContactBasedEntityDetails.ADDRESS, ContactBasedEntityDetails.ADDRESS2), user.getId()); when(ctxMock.getUserService()).thenReturn(userServiceMock); when(userServiceMock.findUserByIdAsync(eq(TENANT_ID), eq(user.getId()))).thenReturn(Futures.immediateFuture(user)); @@ -283,7 +284,7 @@ public class TbGetCustomerDetailsNodeTest { edge.setId(new EdgeId(UUID.randomUUID())); edge.setCustomerId(customer.getId()); - prepareMsgAndConfig(FetchTo.DATA, List.of(ContactBasedEntityDetails.ZIP, ContactBasedEntityDetails.ADDRESS, ContactBasedEntityDetails.ADDRESS2), edge.getId()); + prepareMsgAndConfig(TbMsgSource.DATA, List.of(ContactBasedEntityDetails.ZIP, ContactBasedEntityDetails.ADDRESS, ContactBasedEntityDetails.ADDRESS2), edge.getId()); when(ctxMock.getTenantId()).thenReturn(TENANT_ID); @@ -315,7 +316,7 @@ public class TbGetCustomerDetailsNodeTest { edge.setId(new EdgeId(UUID.randomUUID())); edge.setCustomerId(customer.getId()); - prepareMsgAndConfig(FetchTo.DATA, List.of(ContactBasedEntityDetails.ZIP, ContactBasedEntityDetails.ADDRESS, ContactBasedEntityDetails.ADDRESS2), edge.getId()); + prepareMsgAndConfig(TbMsgSource.DATA, List.of(ContactBasedEntityDetails.ZIP, ContactBasedEntityDetails.ADDRESS, ContactBasedEntityDetails.ADDRESS2), edge.getId()); when(ctxMock.getTenantId()).thenReturn(TENANT_ID); @@ -344,7 +345,7 @@ public class TbGetCustomerDetailsNodeTest { device.setId(new DeviceId(UUID.randomUUID())); device.setName("Thermostat"); - prepareMsgAndConfig(FetchTo.DATA, List.of(ContactBasedEntityDetails.ZIP, ContactBasedEntityDetails.ADDRESS, ContactBasedEntityDetails.ADDRESS2), device.getId()); + prepareMsgAndConfig(TbMsgSource.DATA, List.of(ContactBasedEntityDetails.ZIP, ContactBasedEntityDetails.ADDRESS, ContactBasedEntityDetails.ADDRESS2), device.getId()); when(ctxMock.getTenantId()).thenReturn(TENANT_ID); @@ -382,7 +383,7 @@ public class TbGetCustomerDetailsNodeTest { device.setId(new DeviceId(UUID.randomUUID())); device.setCustomerId(customer.getId()); - prepareMsgAndConfig(FetchTo.DATA, List.of(ContactBasedEntityDetails.ADDITIONAL_INFO), device.getId()); + prepareMsgAndConfig(TbMsgSource.DATA, List.of(ContactBasedEntityDetails.ADDITIONAL_INFO), device.getId()); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(deviceServiceMock.findDeviceByIdAsync(eq(TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); @@ -410,7 +411,7 @@ public class TbGetCustomerDetailsNodeTest { var dashboard = new Dashboard(); dashboard.setId(new DashboardId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.METADATA, List.of(ContactBasedEntityDetails.STATE), dashboard.getId()); + prepareMsgAndConfig(TbMsgSource.METADATA, List.of(ContactBasedEntityDetails.STATE), dashboard.getId()); // WHEN node.onMsg(ctxMock, msg); @@ -443,7 +444,7 @@ public class TbGetCustomerDetailsNodeTest { Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); } - private void prepareMsgAndConfig(FetchTo fetchTo, List detailsList, EntityId originator) { + private void prepareMsgAndConfig(TbMsgSource fetchTo, List detailsList, EntityId originator) { config.setDetailsList(detailsList); config.setFetchTo(fetchTo); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index dfc80d1752..d681deb9c0 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; @@ -100,8 +101,8 @@ public class TbGetOriginatorFieldsNodeTest { "name", "originatorName", "type", "originatorType")); assertThat(config.isIgnoreNullStrings()).isEqualTo(false); - assertThat(config.getFetchTo()).isEqualTo(FetchTo.METADATA); - assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); + assertThat(config.getFetchTo()).isEqualTo(TbMsgSource.METADATA); + assertThat(node.fetchTo).isEqualTo(TbMsgSource.METADATA); } @Test @@ -112,7 +113,7 @@ public class TbGetOriginatorFieldsNodeTest { "title", "originatorTitle", "country", "originatorCountry")); config.setIgnoreNullStrings(true); - config.setFetchTo(FetchTo.DATA); + config.setFetchTo(TbMsgSource.DATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); // WHEN @@ -125,14 +126,14 @@ public class TbGetOriginatorFieldsNodeTest { "title", "originatorTitle", "country", "originatorCountry")); assertThat(config.isIgnoreNullStrings()).isEqualTo(true); - assertThat(config.getFetchTo()).isEqualTo(FetchTo.DATA); - assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); + assertThat(config.getFetchTo()).isEqualTo(TbMsgSource.DATA); + assertThat(node.fetchTo).isEqualTo(TbMsgSource.DATA); } @Test public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN - node.fetchTo = FetchTo.DATA; + node.fetchTo = TbMsgSource.DATA; msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN @@ -156,10 +157,10 @@ public class TbGetOriginatorFieldsNodeTest { "type", "originatorType", "label", "originatorLabel")); config.setIgnoreNullStrings(true); - config.setFetchTo(FetchTo.DATA); + config.setFetchTo(TbMsgSource.DATA); node.config = config; - node.fetchTo = FetchTo.DATA; + node.fetchTo = TbMsgSource.DATA; var msgMetaData = new TbMsgMetaData(); var msgData = "{\"temp\":42,\"humidity\":77}"; msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); @@ -198,10 +199,10 @@ public class TbGetOriginatorFieldsNodeTest { "type", "originatorType", "label", "originatorLabel")); config.setIgnoreNullStrings(true); - config.setFetchTo(FetchTo.DATA); + config.setFetchTo(TbMsgSource.DATA); node.config = config; - node.fetchTo = FetchTo.DATA; + node.fetchTo = TbMsgSource.DATA; var msgMetaData = new TbMsgMetaData(); var msgData = "{\"temp\":42,\"humidity\":77}"; msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); @@ -239,10 +240,10 @@ public class TbGetOriginatorFieldsNodeTest { "type", "originatorType", "label", "originatorLabel")); config.setIgnoreNullStrings(true); - config.setFetchTo(FetchTo.METADATA); + config.setFetchTo(TbMsgSource.METADATA); node.config = config; - node.fetchTo = FetchTo.METADATA; + node.fetchTo = TbMsgSource.METADATA; var msgMetaData = new TbMsgMetaData(Map.of( "testKey1", "testValue1", "testKey2", "123")); @@ -287,10 +288,10 @@ public class TbGetOriginatorFieldsNodeTest { "type", "originatorType", "label", "originatorLabel")); config.setIgnoreNullStrings(false); - config.setFetchTo(FetchTo.METADATA); + config.setFetchTo(TbMsgSource.METADATA); node.config = config; - node.fetchTo = FetchTo.METADATA; + node.fetchTo = TbMsgSource.METADATA; var msgMetaData = new TbMsgMetaData(Map.of( "testKey1", "testValue1", "testKey2", "123")); @@ -345,10 +346,10 @@ public class TbGetOriginatorFieldsNodeTest { "type", "originatorType", "label", "originatorLabel")); config.setIgnoreNullStrings(false); - config.setFetchTo(FetchTo.METADATA); + config.setFetchTo(TbMsgSource.METADATA); node.config = config; - node.fetchTo = FetchTo.METADATA; + node.fetchTo = TbMsgSource.METADATA; var msgMetaData = new TbMsgMetaData(Map.of( "testKey1", "testValue1", "testKey2", "123")); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index 0965327df0..dcf5c22ea5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -33,6 +33,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.data.RelationsQuery; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DataConstants; @@ -156,7 +157,7 @@ public class TbGetRelatedAttributeNodeTest { assertThat(nodeConfig).isEqualTo(config); assertThat(nodeConfig.getDataMapping()).isEqualTo(Map.of("serialNumber", "sn")); assertThat(nodeConfig.getDataToFetch()).isEqualTo(DataToFetch.ATTRIBUTES); - assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); + assertThat(node.fetchTo).isEqualTo(TbMsgSource.METADATA); var relationsQuery = new RelationsQuery(); var relationEntityTypeFilter = new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList()); @@ -175,7 +176,7 @@ public class TbGetRelatedAttributeNodeTest { "sourceAttr2", "targetKey2", "sourceAttr3", "targetKey3")); config.setDataToFetch(DataToFetch.LATEST_TELEMETRY); - config.setFetchTo(FetchTo.DATA); + config.setFetchTo(TbMsgSource.DATA); var relationsQuery = new RelationsQuery(); var relationEntityTypeFilter = new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList()); @@ -198,7 +199,7 @@ public class TbGetRelatedAttributeNodeTest { "sourceAttr3", "targetKey3" )); assertThat(nodeConfig.getDataToFetch()).isEqualTo(DataToFetch.LATEST_TELEMETRY); - assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); + assertThat(node.fetchTo).isEqualTo(TbMsgSource.DATA); assertThat(nodeConfig.getRelationsQuery()).isEqualTo(relationsQuery); } @@ -221,7 +222,7 @@ public class TbGetRelatedAttributeNodeTest { @Test public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN - node.fetchTo = FetchTo.DATA; + node.fetchTo = TbMsgSource.DATA; msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN @@ -235,7 +236,7 @@ public class TbGetRelatedAttributeNodeTest { @Test public void givenDidNotFindEntity_whenOnMsg_thenShouldTellFailure() { // GIVEN - prepareMsgAndConfig(FetchTo.METADATA, DataToFetch.ATTRIBUTES, DUMMY_DEVICE_ORIGINATOR); + prepareMsgAndConfig(TbMsgSource.METADATA, DataToFetch.ATTRIBUTES, DUMMY_DEVICE_ORIGINATOR); when(ctxMock.getTenantId()).thenReturn(TENANT_ID); @@ -271,7 +272,7 @@ public class TbGetRelatedAttributeNodeTest { var customer = new Customer(new CustomerId(UUID.randomUUID())); var user = new User(new UserId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.DATA, DataToFetch.ATTRIBUTES, customer.getId()); + prepareMsgAndConfig(TbMsgSource.DATA, DataToFetch.ATTRIBUTES, customer.getId()); entityRelation.setFrom(customer.getId()); entityRelation.setTo(user.getId()); @@ -322,7 +323,7 @@ public class TbGetRelatedAttributeNodeTest { var firstCustomer = new Customer(new CustomerId(UUID.randomUUID())); var secondCustomer = new Customer(new CustomerId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.METADATA, DataToFetch.ATTRIBUTES, firstCustomer.getId()); + prepareMsgAndConfig(TbMsgSource.METADATA, DataToFetch.ATTRIBUTES, firstCustomer.getId()); entityRelation.setFrom(firstCustomer.getId()); entityRelation.setTo(secondCustomer.getId()); @@ -373,7 +374,7 @@ public class TbGetRelatedAttributeNodeTest { var dashboard = new Dashboard(new DashboardId(UUID.randomUUID())); var entityView = new EntityView(new EntityViewId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.DATA, DataToFetch.LATEST_TELEMETRY, dashboard.getId()); + prepareMsgAndConfig(TbMsgSource.DATA, DataToFetch.LATEST_TELEMETRY, dashboard.getId()); entityRelation.setFrom(dashboard.getId()); entityRelation.setTo(entityView.getId()); @@ -424,7 +425,7 @@ public class TbGetRelatedAttributeNodeTest { var tenant = new Tenant(new TenantId(UUID.randomUUID())); var device = new Device(new DeviceId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.METADATA, DataToFetch.LATEST_TELEMETRY, tenant.getId()); + prepareMsgAndConfig(TbMsgSource.METADATA, DataToFetch.LATEST_TELEMETRY, tenant.getId()); entityRelation.setFrom(tenant.getId()); entityRelation.setTo(device.getId()); @@ -477,7 +478,7 @@ public class TbGetRelatedAttributeNodeTest { device.setName("Device Name"); var asset = new Asset(new AssetId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.DATA, DataToFetch.FIELDS, asset.getId()); + prepareMsgAndConfig(TbMsgSource.DATA, DataToFetch.FIELDS, asset.getId()); entityRelation.setFrom(asset.getId()); entityRelation.setTo(device.getId()); @@ -517,7 +518,7 @@ public class TbGetRelatedAttributeNodeTest { device.setName("Device Name"); var asset = new Asset(new AssetId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.METADATA, DataToFetch.FIELDS, asset.getId()); + prepareMsgAndConfig(TbMsgSource.METADATA, DataToFetch.FIELDS, asset.getId()); entityRelation.setFrom(asset.getId()); entityRelation.setTo(device.getId()); @@ -567,7 +568,7 @@ public class TbGetRelatedAttributeNodeTest { Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); } - private void prepareMsgAndConfig(FetchTo fetchTo, DataToFetch dataToFetch, EntityId originator) { + private void prepareMsgAndConfig(TbMsgSource fetchTo, DataToFetch dataToFetch, EntityId originator) { config.setDataToFetch(dataToFetch); config.setFetchTo(fetchTo); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index c7a23a512b..87b34cffe0 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -31,6 +31,7 @@ import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; @@ -141,7 +142,7 @@ public class TbGetTenantAttributeNodeTest { assertThat(node.config).isEqualTo(config); assertThat(config.getDataMapping()).isEqualTo(Map.of("alarmThreshold", "threshold")); assertThat(config.getDataToFetch()).isEqualTo(DataToFetch.ATTRIBUTES); - assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); + assertThat(node.fetchTo).isEqualTo(TbMsgSource.METADATA); } @Test @@ -152,7 +153,7 @@ public class TbGetTenantAttributeNodeTest { "sourceAttr2", "targetKey2", "sourceAttr3", "targetKey3")); config.setDataToFetch(DataToFetch.LATEST_TELEMETRY); - config.setFetchTo(FetchTo.DATA); + config.setFetchTo(TbMsgSource.DATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); // WHEN @@ -165,7 +166,7 @@ public class TbGetTenantAttributeNodeTest { "sourceAttr2", "targetKey2", "sourceAttr3", "targetKey3")); assertThat(config.getDataToFetch()).isEqualTo(DataToFetch.LATEST_TELEMETRY); - assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); + assertThat(node.fetchTo).isEqualTo(TbMsgSource.DATA); } @Test @@ -187,7 +188,7 @@ public class TbGetTenantAttributeNodeTest { @Test public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN - node.fetchTo = FetchTo.DATA; + node.fetchTo = TbMsgSource.DATA; msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN @@ -203,7 +204,7 @@ public class TbGetTenantAttributeNodeTest { // GIVEN var deviceId = new DeviceId(UUID.randomUUID()); - prepareMsgAndConfig(FetchTo.DATA, DataToFetch.ATTRIBUTES, deviceId); + prepareMsgAndConfig(TbMsgSource.DATA, DataToFetch.ATTRIBUTES, deviceId); List attributesList = List.of( new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L), @@ -244,7 +245,7 @@ public class TbGetTenantAttributeNodeTest { @Test public void givenFetchAttributesToMetaData_whenOnMsg_thenShouldFetchAttributesToMetaData() { // GIVEN - prepareMsgAndConfig(FetchTo.METADATA, DataToFetch.ATTRIBUTES, TENANT_ID); + prepareMsgAndConfig(TbMsgSource.METADATA, DataToFetch.ATTRIBUTES, TENANT_ID); List attributesList = List.of( new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L), @@ -287,7 +288,7 @@ public class TbGetTenantAttributeNodeTest { // GIVEN var customerId = new CustomerId(UUID.randomUUID()); - prepareMsgAndConfig(FetchTo.DATA, DataToFetch.LATEST_TELEMETRY, customerId); + prepareMsgAndConfig(TbMsgSource.DATA, DataToFetch.LATEST_TELEMETRY, customerId); List timeseries = List.of( new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")), @@ -330,7 +331,7 @@ public class TbGetTenantAttributeNodeTest { // GIVEN var ruleChainId = new RuleChainId(UUID.randomUUID()); - prepareMsgAndConfig(FetchTo.METADATA, DataToFetch.LATEST_TELEMETRY, ruleChainId); + prepareMsgAndConfig(TbMsgSource.METADATA, DataToFetch.LATEST_TELEMETRY, ruleChainId); List timeseries = List.of( new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")), @@ -379,7 +380,7 @@ public class TbGetTenantAttributeNodeTest { Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); } - private void prepareMsgAndConfig(FetchTo fetchTo, DataToFetch dataToFetch, EntityId originator) { + private void prepareMsgAndConfig(TbMsgSource fetchTo, DataToFetch dataToFetch, EntityId originator) { config.setDataMapping(Map.of( "sourceKey1", "targetKey1", "${metaDataPattern1}", "$[messageBodyPattern1]", diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java index 23b1abea73..bfbaa9a92e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.util.ContactBasedEntityDetails; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -103,14 +104,14 @@ public class TbGetTenantDetailsNodeTest { public void givenDefaultConfig_whenInit_thenOK() { // THEN assertThat(config.getDetailsList()).isEqualTo(Collections.emptyList()); - assertThat(config.getFetchTo()).isEqualTo(FetchTo.DATA); + assertThat(config.getFetchTo()).isEqualTo(TbMsgSource.DATA); } @Test public void givenCustomConfig_whenInit_thenOK() throws TbNodeException { // GIVEN config.setDetailsList(List.of(ContactBasedEntityDetails.ID, ContactBasedEntityDetails.PHONE)); - config.setFetchTo(FetchTo.METADATA); + config.setFetchTo(TbMsgSource.METADATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); // WHEN @@ -119,14 +120,14 @@ public class TbGetTenantDetailsNodeTest { // THEN assertThat(node.config).isEqualTo(config); assertThat(config.getDetailsList()).isEqualTo(List.of(ContactBasedEntityDetails.ID, ContactBasedEntityDetails.PHONE)); - assertThat(config.getFetchTo()).isEqualTo(FetchTo.METADATA); - assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); + assertThat(config.getFetchTo()).isEqualTo(TbMsgSource.METADATA); + assertThat(node.fetchTo).isEqualTo(TbMsgSource.METADATA); } @Test public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN - node.fetchTo = FetchTo.DATA; + node.fetchTo = TbMsgSource.DATA; msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN @@ -140,7 +141,7 @@ public class TbGetTenantDetailsNodeTest { @Test public void givenAllEntityDetailsAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchAllToData() { // GIVEN - prepareMsgAndConfig(FetchTo.DATA, List.of(ContactBasedEntityDetails.values())); + prepareMsgAndConfig(TbMsgSource.DATA, List.of(ContactBasedEntityDetails.values())); mockFindTenant(); @@ -173,7 +174,7 @@ public class TbGetTenantDetailsNodeTest { @Test public void givenSomeEntityDetailsAndFetchToMetadata_whenOnMsg_thenShouldTellSuccessAndFetchSomeToMetaData() { // GIVEN - prepareMsgAndConfig(FetchTo.METADATA, List.of(ContactBasedEntityDetails.ID, ContactBasedEntityDetails.TITLE, ContactBasedEntityDetails.PHONE)); + prepareMsgAndConfig(TbMsgSource.METADATA, List.of(ContactBasedEntityDetails.ID, ContactBasedEntityDetails.TITLE, ContactBasedEntityDetails.PHONE)); mockFindTenant(); @@ -202,7 +203,7 @@ public class TbGetTenantDetailsNodeTest { tenant.setAddress(null); tenant.setAddress2(null); - prepareMsgAndConfig(FetchTo.DATA, List.of(ContactBasedEntityDetails.ZIP, ContactBasedEntityDetails.ADDRESS, ContactBasedEntityDetails.ADDRESS2)); + prepareMsgAndConfig(TbMsgSource.DATA, List.of(ContactBasedEntityDetails.ZIP, ContactBasedEntityDetails.ADDRESS, ContactBasedEntityDetails.ADDRESS2)); mockFindTenant(); @@ -222,7 +223,7 @@ public class TbGetTenantDetailsNodeTest { @Test public void givenDidNotFindTenant_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() { // GIVEN - prepareMsgAndConfig(FetchTo.DATA, List.of(ContactBasedEntityDetails.ZIP, ContactBasedEntityDetails.ADDRESS, ContactBasedEntityDetails.ADDRESS2)); + prepareMsgAndConfig(TbMsgSource.DATA, List.of(ContactBasedEntityDetails.ZIP, ContactBasedEntityDetails.ADDRESS, ContactBasedEntityDetails.ADDRESS2)); when(ctxMock.getTenantId()).thenReturn(tenant.getId()); when(ctxMock.getTenantService()).thenReturn(tenantServiceMock); @@ -246,7 +247,7 @@ public class TbGetTenantDetailsNodeTest { // GIVEN tenant.setAdditionalInfo(JacksonUtil.toJsonNode("{\"someProperty\":\"someValue\",\"description\":null}")); - prepareMsgAndConfig(FetchTo.DATA, List.of(ContactBasedEntityDetails.ADDITIONAL_INFO)); + prepareMsgAndConfig(TbMsgSource.DATA, List.of(ContactBasedEntityDetails.ADDITIONAL_INFO)); mockFindTenant(); @@ -274,7 +275,7 @@ public class TbGetTenantDetailsNodeTest { Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); } - private void prepareMsgAndConfig(FetchTo fetchTo, List detailsList) { + private void prepareMsgAndConfig(TbMsgSource fetchTo, List detailsList) { config.setDetailsList(detailsList); config.setFetchTo(fetchTo); From fabd26dc2c9e4d5d090e5f6027f53db74d59caf5 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 3 Aug 2023 16:13:41 +0300 Subject: [PATCH 163/168] fix mistakenly added override of init method in TbGpsGeofencingFilterNode ShvaykaD Moments ago --- .../rule/engine/geo/TbGpsGeofencingFilterNode.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java index a9276bbb31..20e7039be4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java @@ -18,7 +18,6 @@ package org.thingsboard.rule.engine.geo; 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.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -65,11 +64,6 @@ import org.thingsboard.server.common.msg.TbMsg; configDirective = "tbFilterNodeGpsGeofencingConfig") public class TbGpsGeofencingFilterNode extends AbstractGeofencingNode { - @Override - public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { - super.init(ctx, configuration); - } - @Override public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { ctx.tellNext(msg, checkMatches(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); From 544f29f0e19898b3667de97643e1ee2d9779c4c3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 3 Aug 2023 19:26:35 +0300 Subject: [PATCH 164/168] UI: Responsive layout improvements. --- .../add-widget-dialog.component.html | 5 +- .../dashboard-image-dialog.component.html | 16 +-- .../dashboard-widget-select.component.scss | 2 - .../value-card-basic-config.component.html | 14 +- .../basic/common/data-key-row.component.html | 2 +- .../data-key-config-dialog.component.html | 2 +- .../config/data-key-config.component.html | 130 +++++++++--------- .../widget/config/data-keys.component.html | 4 +- .../widget/config/data-keys.component.scss | 3 + .../widget/config/datasources.component.html | 24 ++-- .../widget/config/datasources.component.scss | 43 +++++- .../lib/navigation-card-widget.component.html | 2 +- .../alarms-table-key-settings.component.html | 8 +- ...larms-table-widget-settings.component.html | 6 +- ...entities-table-key-settings.component.html | 8 +- ...ities-table-widget-settings.component.html | 10 +- ...meseries-table-key-settings.component.html | 8 +- ...s-table-latest-key-settings.component.html | 8 +- ...eries-table-widget-settings.component.html | 6 +- .../value-card-widget-settings.component.html | 8 +- .../chart/flot-key-settings.component.html | 8 +- .../chart/flot-threshold.component.html | 2 +- .../chart/flot-threshold.component.scss | 7 + .../chart/flot-widget-settings.component.html | 24 ++-- .../background-settings-panel.component.html | 2 +- .../background-settings-panel.component.ts | 2 +- .../common/image-cards-select.component.html | 2 +- .../common/value-source.component.html | 6 +- .../components/image-input.component.html | 66 ++++----- .../components/image-input.component.scss | 118 +++++++--------- .../components/image-input.component.ts | 3 - .../components/toggle-header.component.scss | 4 +- .../components/toggle-header.component.ts | 37 +++-- .../components/unit-input.component.html | 2 +- .../assets/locale/locale.constant-ca_ES.json | 1 - .../assets/locale/locale.constant-en_US.json | 4 +- .../assets/locale/locale.constant-es_ES.json | 1 - .../assets/locale/locale.constant-zh_CN.json | 1 - .../assets/locale/locale.constant-zh_TW.json | 1 - ui-ngx/src/form.scss | 25 ++-- 40 files changed, 330 insertions(+), 295 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html index 47f8634462..7b6939df52 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html @@ -18,8 +18,9 @@
-

{{'widget.add' | translate}}: {{data.widgetInfo.widgetName}}

-
+

{{'widget.add' | translate}}:

+ {{data.widgetInfo.widgetName}} +
{{ 'widget.basic-mode' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.html index 9fdfa56744..9019161c29 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.html @@ -28,7 +28,7 @@
-
+
Top % @@ -53,14 +53,14 @@
-
+
+
-
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss index e86f828111..a82f9c2f8b 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss @@ -45,8 +45,6 @@ } .preview { - width: 100%; - height: 100%; max-width: 100%; max-height: 100%; object-fit: contain; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html index 788c997740..356612564f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html @@ -45,7 +45,7 @@ {{ 'widgets.value-card.label' | translate }}
- +
- + @@ -75,10 +75,10 @@
widgets.value-card.value
- - + + -
widget-config.decimals-suffix
+
widget-config.decimals-suffix
@@ -87,11 +87,11 @@
-
+
{{ 'widgets.value-card.date' | translate }} -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html index 62f34f3d28..63f936195a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html @@ -28,7 +28,7 @@ (removed)="removeKey()">
-
+
{{ 'datakey.configuration' | translate }}
+
+ datakey.data-generation-func +
+ + +
datakey.aggregation @@ -80,72 +92,62 @@ {{ 'datakey.aggregation-type-hint-common' | translate }}
-
- datakey.delta-calculation - - - - - {{ 'datakey.enable-delta-calculation' | translate }} - - {{ 'datakey.enable-delta-calculation-hint' | translate }} - - - -
- - widgets.chart.time-for-comparison - - - {{ 'widgets.chart.time-for-comparison-previous-interval' | translate }} - - - {{ 'widgets.chart.time-for-comparison-days' | translate }} - - - {{ 'widgets.chart.time-for-comparison-weeks' | translate }} - - - {{ 'widgets.chart.time-for-comparison-months' | translate }} - - - {{ 'widgets.chart.time-for-comparison-years' | translate }} - - - {{ 'widgets.chart.time-for-comparison-custom-interval' | translate }} - - - - - widgets.chart.custom-interval-value - - - - datakey.delta-calculation-result - - - {{ comparisonResultTypeTranslations.get(comparisonResultTypes[comparisonResultType]) | translate }} - - - -
-
-
-
-
- datakey.data-generation-func -
- - -
+
+
datakey.delta-calculation
+ + + + + {{ 'datakey.enable-delta-calculation' | translate }} + + {{ 'datakey.enable-delta-calculation-hint' | translate }} + + + +
+ + widgets.chart.time-for-comparison + + + {{ 'widgets.chart.time-for-comparison-previous-interval' | translate }} + + + {{ 'widgets.chart.time-for-comparison-days' | translate }} + + + {{ 'widgets.chart.time-for-comparison-weeks' | translate }} + + + {{ 'widgets.chart.time-for-comparison-months' | translate }} + + + {{ 'widgets.chart.time-for-comparison-years' | translate }} + + + {{ 'widgets.chart.time-for-comparison-custom-interval' | translate }} + + + + + widgets.chart.custom-interval-value + + + + datakey.delta-calculation-result + + + {{ comparisonResultTypeTranslations.get(comparisonResultTypes[comparisonResultType]) | translate }} + + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html index e024af20ee..3226ffed5e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html @@ -47,7 +47,7 @@ drag_indicator
-
+
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss index 1664dafb7f..574d1df38e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss @@ -70,6 +70,9 @@ text-overflow: ellipsis; white-space: nowrap; } + &.tb-chip-icon { + min-width: 24px; + } .mat-icon.tb-datakey-icon { margin-right: 4px; margin-left: 4px; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/datasources.component.html b/ui-ngx/src/app/modules/home/components/widget/config/datasources.component.html index 2e4a984858..0ee6a7c508 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/datasources.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/datasources.component.html @@ -44,29 +44,27 @@ [cdkDropListDisabled]="dragDisabled" formArrayName="datasources"> -
-
+
+
{{$index + 1}}
-
-
- +
+
+ -
-
- - + + {{ 'widgets.table.use-cell-style-function' | translate }} @@ -86,8 +86,8 @@
- - + + {{ 'widgets.table.use-cell-content-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.html index 4ec8f31be6..e304def2c8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.html @@ -94,7 +94,7 @@ {{ 'widgets.table.display-pagination' | translate }} -
+
widgets.table.default-page-size
@@ -104,8 +104,8 @@
widgets.table.rows
- - + + {{ 'widgets.table.use-row-style-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-key-settings.component.html index 02d51ee325..faad357026 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-key-settings.component.html @@ -62,8 +62,8 @@
- - + + {{ 'widgets.table.use-cell-style-function' | translate }} @@ -86,8 +86,8 @@
- - + + {{ 'widgets.table.use-cell-content-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html index f8867b9f6a..c9c57d8843 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html @@ -41,7 +41,7 @@
widgets.table.columns
-
+
{{ 'widgets.table.display-entity-name' | translate }} @@ -49,7 +49,7 @@
-
+
{{ 'widgets.table.display-entity-label' | translate }} @@ -91,7 +91,7 @@ {{ 'widgets.table.display-pagination' | translate }} -
+
widgets.table.default-page-size
@@ -101,8 +101,8 @@
widgets.table.rows
- - + + {{ 'widgets.table.use-row-style-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-key-settings.component.html index 6c79eed622..165f4eb0f7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-key-settings.component.html @@ -50,8 +50,8 @@
- - + + {{ 'widgets.table.use-cell-style-function' | translate }} @@ -74,8 +74,8 @@
- - + + {{ 'widgets.table.use-cell-content-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-latest-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-latest-key-settings.component.html index 97cbe28ae9..fd34b66af6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-latest-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-latest-key-settings.component.html @@ -59,8 +59,8 @@
- - + + {{ 'widgets.table.use-cell-style-function' | translate }} @@ -83,8 +83,8 @@
- - + + {{ 'widgets.table.use-cell-content-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html index 170d460f92..d70ae9964b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html @@ -59,7 +59,7 @@ {{ 'widgets.table.display-pagination' | translate }} -
+
widgets.table.default-page-size
@@ -78,8 +78,8 @@ {{ 'widgets.table.hide-empty-lines' | translate }} - - + + {{ 'widgets.table.use-row-style-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html index 423c727a8d..71a9b60118 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html @@ -44,8 +44,8 @@ {{ 'widgets.value-card.icon' | translate }} -
- +
+ @@ -67,11 +67,11 @@
-
+
{{ 'widgets.value-card.date' | translate }} -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html index 96ff4d8559..a424b61606 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html @@ -32,8 +32,8 @@
- - + + {{ 'widgets.chart.show-line' | translate }} @@ -65,8 +65,8 @@
- - + + {{ 'widgets.chart.show-points' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html index 6f08b5f3f7..be2ae39255 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html @@ -17,7 +17,7 @@ -->
- +
{{ thresholdText() }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss index 150d516894..81f5d17bc6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss @@ -29,6 +29,9 @@ display: flex; flex-direction: row; align-items: stretch; + .mat-content { + overflow: hidden; + } .tb-threshold-header { flex: 1; display: flex; @@ -36,6 +39,7 @@ gap: 16px; align-items: center; padding-left: 16px; + overflow: hidden; .mat-divider-vertical { height: 100%; } @@ -47,6 +51,9 @@ font-weight: 400; line-height: 16px; letter-spacing: 0.15px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } .mat-expansion-indicator { margin-right: 22px; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html index 5dd7eaa2dc..4ea648c3b4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html @@ -77,8 +77,8 @@
widget-config.legend
- - + + {{ 'widget-config.legend' | translate }} @@ -119,8 +119,8 @@
widgets.chart.ticks
- - + + {{ 'widgets.chart.ticks' | translate }} @@ -171,8 +171,8 @@
widgets.chart.ticks
- - + + {{ 'widgets.chart.ticks' | translate }} @@ -234,8 +234,8 @@
widgets.chart.tooltip
- - + + {{ 'widgets.chart.tooltip' | translate }} @@ -274,8 +274,8 @@
widgets.chart.comparison-settings
- - + + {{ 'widgets.chart.enable-comparison' | translate }} @@ -350,8 +350,8 @@
widgets.chart.custom-legend-settings
- - + + {{ 'widgets.chart.enable-custom-legend' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html index ca5d4bc8b9..42c4472cca 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html @@ -20,7 +20,7 @@
widgets.background.background
- + {{ backgroundTypeTranslationsMap.get(type) | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts index 51d2ddec1b..465d3383e2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts @@ -94,7 +94,7 @@ export class BackgroundSettingsPanelComponent extends PageComponent implements O } applyColorSettings() { - const backgroundSettings = this.backgroundSettingsFormGroup.value; + const backgroundSettings = this.backgroundSettingsFormGroup.getRawValue(); this.backgroundSettingsApplied.emit(backgroundSettings); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.html index eb2bbd6711..52bd5907bd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.html @@ -17,7 +17,7 @@ -->
- +
{{ label }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html index 3cc771e94f..726aed776e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html @@ -17,7 +17,7 @@ -->
- + {{ 'widgets.value-source.predefined-value' | translate }} @@ -32,7 +32,7 @@
-
+
widgets.value-source.source-entity-alias
-
+
widgets.value-source.source-entity-attribute
-
-
-
-
{{ (disabled ? 'dashboard.empty-image' : 'dashboard.no-image') | translate }}
- -
-
- -
-
-
-
-
- cloud_upload - image-input.drop-image-or - - +
+
+
{{ 'dashboard.empty-image' | translate }}
+
-
-
-
-
+
+
+ cloud_upload +
+ image-input.drag-and-drop +
+ image-input.or + + +
+
+
+
+ +
dashboard.maximum-upload-file-size
diff --git a/ui-ngx/src/app/shared/components/image-input.component.scss b/ui-ngx/src/app/shared/components/image-input.component.scss index a776bc8cd7..b7d05bef8d 100644 --- a/ui-ngx/src/app/shared/components/image-input.component.scss +++ b/ui-ngx/src/app/shared/components/image-input.component.scss @@ -15,9 +15,8 @@ */ @import "../../../scss/constants"; -$containerHeight: 120px !default; -$previewContainerWidth: 168px !default; -$previewSize: 96px !default; +$containerHeight: 96px !default; +$previewSize: 78px !default; :host { @@ -31,30 +30,39 @@ $previewSize: 96px !default; } .tb-image-select-container { - position: relative; width: 100%; height: $containerHeight; + display: flex; + gap: 12px; + align-items: center; } .image-container { - position: relative; - float: left; - height: $containerHeight; - padding: 12px; - margin-right: 8px; - background: rgba(0, 0, 0, 0.03); + background: #F3F6FA; border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.05); + padding: 8px 12px 8px 8px; + display: flex; + align-items: center; + gap: 12px; + &.disabled { + padding: 8px; + } } - .image-content-container { - background: #FFFFFF; - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 4px; - padding-left: 8px; + .tb-image-preview-container { + width: $previewSize; height: $previewSize; - &.no-padding { - padding-left: 0px; - } + border: 1px solid rgba(0, 0, 0, 0.12); + background: #fff; + display: flex; + align-items: center; + justify-content: center; + } + + .tb-image-preview-text { + font-size: 14px; + text-align: center; } .tb-image-preview { @@ -64,57 +72,18 @@ $previewSize: 96px !default; max-height: $previewSize - 2px; } - .tb-image-preview-container { - position: relative; - float: left; - width: $previewSize; - height: $previewSize; - margin-top: -1px; - margin-bottom: -1px; - border: 1px solid rgba(0, 0, 0, 0.54); - - div { - width: 100%; - font-size: 18px; - text-align: center; - } - - div, - .tb-image-preview { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - } - - .tb-image-clear-container { - position: relative; - float: right; - height: $previewSize; - display: flex; - align-items: center; - &.full-height { - height: $containerHeight; - } - } - .file-input { display: none; } .tb-flow-drop { - position: relative; - height: $containerHeight; + height: 100%; + flex: 1; overflow: hidden; border: 2px dashed rgba(0, 0, 0, 0.2); border-radius: 4px; box-sizing: border-box; - &.float-left { - float: left; - } - .upload-label { width: 100%; height: 100%; @@ -123,11 +92,29 @@ $previewSize: 96px !default; flex-direction: row; justify-content: center; align-items: center; - font-size: 16px; - color: rgba(0, 0, 0, 0.54); - text-align: center; + gap: 8px; .mat-icon { - margin-right: 17px; + color: rgba(0,0,0,0.12); + } + .upload-text-area { + display: flex; + flex-direction: column; + align-items: center; + font-size: 16px; + line-height: 24px; + color: rgba(0, 0, 0, 0.54); + text-align: center; + .hide-xs { + @media #{$mat-xs} { + display: none; + } + } + } + .upload-button-area { + display: flex; + justify-content: center; + align-items: flex-start; + gap: 6px; } } } @@ -138,13 +125,14 @@ $previewSize: 96px !default; } :host ::ng-deep { - button.browse-file { + button.mat-mdc-button.mat-mdc-button-base.browse-file { padding: 0; + min-width: 0; + height: 24px; font-size: 16px; label { display: block; cursor: pointer; - padding: 0 16px; } } } diff --git a/ui-ngx/src/app/shared/components/image-input.component.ts b/ui-ngx/src/app/shared/components/image-input.component.ts index 026b64a413..f5b80aa1e0 100644 --- a/ui-ngx/src/app/shared/components/image-input.component.ts +++ b/ui-ngx/src/app/shared/components/image-input.component.ts @@ -65,9 +65,6 @@ export class ImageInputComponent extends PageComponent implements AfterViewInit, @Input() disabled: boolean; - @Input() - showClearButton = true; - @Input() showPreview = true; diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.scss b/ui-ngx/src/app/shared/components/toggle-header.component.scss index 7a41032961..ac24f7bf29 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.scss +++ b/ui-ngx/src/app/shared/components/toggle-header.component.scss @@ -29,10 +29,12 @@ display: block; } } - .tb-toggle-container { + .tb-toggle-container, .tb-toggle-header-select { display: inline-grid; grid-column: 2; overflow: hidden; + } + .tb-toggle-container { &.tb-disable-pagination { overflow: visible; } diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.ts b/ui-ngx/src/app/shared/components/toggle-header.component.ts index a7bddfc7b4..5781f9fac9 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.ts +++ b/ui-ngx/src/app/shared/components/toggle-header.component.ts @@ -16,7 +16,7 @@ import { AfterContentChecked, - AfterContentInit, + AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectorRef, Component, @@ -117,7 +117,8 @@ export abstract class _ToggleBase extends PageComponent implements AfterContentI templateUrl: './toggle-header.component.html', styleUrls: ['./toggle-header.component.scss'] }) -export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterViewInit, AfterContentInit, AfterContentChecked, OnDestroy { +export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterViewInit, AfterContentInit, + AfterContentChecked, AfterViewChecked, OnDestroy { @ViewChild('toggleGroup', {static: false}) toggleGroup: ElementRef; @@ -130,6 +131,7 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV @HostBinding('class.tb-toggle-header-pagination-controls-enabled') private showPaginationControls = false; + private _showPaginationControlsChanged = false; private toggleGroupResize$: ResizeObserver; @@ -254,6 +256,14 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV } } + ngAfterViewChecked() { + if (this._showPaginationControlsChanged) { + this.scrollToToggleOptionValue(); + this._showPaginationControlsChanged = false; + this.cd.markForCheck(); + } + } + trackByHeaderOption(index: number, option: ToggleHeaderOption){ return option.value; } @@ -301,10 +311,13 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV private scrollToToggleOptionValue() { if (this.buttonToggleGroup && this.buttonToggleGroup.selected) { const selectedToggleButton = this.buttonToggleGroup.selected as MatButtonToggle; + const index = this.options.findIndex(o => o.value === selectedToggleButton.value); + const isLast = index === this.options.length - 1; + const isFirst = index === 0; const viewLength = this.toggleGroupContainer.nativeElement.offsetWidth; const {offsetLeft, offsetWidth} = (selectedToggleButton._buttonElement.nativeElement.offsetParent as HTMLElement); - const labelBeforePos = offsetLeft; // this.toggleGroup.nativeElement.offsetWidth - offsetLeft; - const labelAfterPos = labelBeforePos + offsetWidth; + const labelBeforePos = isFirst ? 0 : offsetLeft; + const labelAfterPos = isLast ? this.toggleGroup.nativeElement.scrollWidth : labelBeforePos + offsetWidth; const beforeVisiblePos = this.scrollDistance; const afterVisiblePos = this.scrollDistance + viewLength; if (labelBeforePos < beforeVisiblePos) { @@ -331,9 +344,7 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV if (!isEnabled) { this.scrollDistance = 0; } else { - setTimeout(() => { - this.scrollToToggleOptionValue(); - }, 0); + this._showPaginationControlsChanged = true; } this.cd.markForCheck(); this.showPaginationControls = isEnabled; @@ -373,11 +384,13 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV } private updateToggleHeaderScrollPosition() { - const scrollDistance = this.scrollDistance; - const translateX = -scrollDistance; - this.toggleGroup.nativeElement.style.transform = `translateX(${Math.round(translateX)}px)`; - if (this.platform.TRIDENT || this.platform.EDGE) { - this.toggleGroupContainer.nativeElement.scrollLeft = 0; + if (this.toggleGroupContainer) { + const scrollDistance = this.scrollDistance; + const translateX = -scrollDistance; + this.toggleGroup.nativeElement.style.transform = `translateX(${Math.round(translateX)}px)`; + if (this.platform.TRIDENT || this.platform.EDGE) { + this.toggleGroupContainer.nativeElement.scrollLeft = 0; + } } } } diff --git a/ui-ngx/src/app/shared/components/unit-input.component.html b/ui-ngx/src/app/shared/components/unit-input.component.html index 0ae14b8ba9..c15b232444 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.html +++ b/ui-ngx/src/app/shared/components/unit-input.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + Date: Thu, 3 Aug 2023 20:20:17 +0300 Subject: [PATCH 165/168] reverted refactoring with "return" use in the if statement & added @Override where required in DefaultTbContext & reverted changes to TbAbstractExternalNode --- .../actors/ruleChain/DefaultTbContext.java | 71 ++++++++++++++----- .../rule/engine/api/TbContext.java | 4 +- .../external/TbAbstractExternalNode.java | 12 +++- .../engine/profile/TbDeviceProfileNode.java | 48 ++++++------- 4 files changed, 89 insertions(+), 46 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 dc03a97254..f5a0cedf08 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 @@ -371,10 +371,12 @@ class DefaultTbContext implements TbContext { return TbMsg.transformMsgOriginator(origMsg, originator); } + @Override public TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId) { return entityActionMsg(customer, customer.getId(), ruleNodeId, ENTITY_CREATED); } + @Override public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { DeviceProfile deviceProfile = null; if (device.getDeviceProfileId() != null) { @@ -383,6 +385,7 @@ class DefaultTbContext implements TbContext { return entityActionMsg(device, device.getId(), ruleNodeId, ENTITY_CREATED, deviceProfile); } + @Override public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { AssetProfile assetProfile = null; if (asset.getAssetProfileId() != null) { @@ -391,18 +394,33 @@ class DefaultTbContext implements TbContext { return entityActionMsg(asset, asset.getId(), ruleNodeId, ENTITY_CREATED, assetProfile); } + @Override + public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { + EntityId originator = alarm.getOriginator(); + HasRuleEngineProfile profile = getRuleEngineProfile(originator); + return entityActionMsg(alarm, originator, ruleNodeId, action, profile); + } + + @Override public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, TbMsgType actionMsgType) { + EntityId originator = alarm.getOriginator(); + HasRuleEngineProfile profile = getRuleEngineProfile(originator); + return entityActionMsg(alarm, originator, ruleNodeId, actionMsgType, profile); + } + + private HasRuleEngineProfile getRuleEngineProfile(EntityId originator) { HasRuleEngineProfile profile = null; - if (EntityType.DEVICE.equals(alarm.getOriginator().getEntityType())) { - DeviceId deviceId = new DeviceId(alarm.getOriginator().getId()); + if (EntityType.DEVICE.equals(originator.getEntityType())) { + DeviceId deviceId = new DeviceId(originator.getId()); profile = mainCtx.getDeviceProfileCache().get(getTenantId(), deviceId); - } else if (EntityType.ASSET.equals(alarm.getOriginator().getEntityType())) { - AssetId assetId = new AssetId(alarm.getOriginator().getId()); + } else if (EntityType.ASSET.equals(originator.getEntityType())) { + AssetId assetId = new AssetId(originator.getId()); profile = mainCtx.getAssetProfileCache().get(getTenantId(), assetId); } - return entityActionMsg(alarm, alarm.getOriginator(), ruleNodeId, actionMsgType, profile); + return profile; } + @Override public TbMsg attributesUpdatedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List attributes) { ObjectNode entityNode = JacksonUtil.newObjectNode(); if (attributes != null) { @@ -411,6 +429,7 @@ class DefaultTbContext implements TbContext { return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_UPDATED, JacksonUtil.toString(entityNode)); } + @Override public TbMsg attributesDeletedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List keys) { ObjectNode entityNode = JacksonUtil.newObjectNode(); ArrayNode attrsArrayNode = entityNode.putArray("attributes"); @@ -423,14 +442,7 @@ class DefaultTbContext implements TbContext { private TbMsg attributesActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, TbMsgType actionMsgType, String msgData) { TbMsgMetaData tbMsgMetaData = getActionMetaData(ruleNodeId); tbMsgMetaData.putValue("scope", scope); - HasRuleEngineProfile profile = null; - if (EntityType.DEVICE.equals(originator.getEntityType())) { - DeviceId deviceId = new DeviceId(originator.getId()); - profile = mainCtx.getDeviceProfileCache().get(getTenantId(), deviceId); - } else if (EntityType.ASSET.equals(originator.getEntityType())) { - AssetId assetId = new AssetId(originator.getId()); - profile = mainCtx.getAssetProfileCache().get(getTenantId(), assetId); - } + HasRuleEngineProfile profile = getRuleEngineProfile(originator); return entityActionMsg(originator, tbMsgMetaData, msgData, actionMsgType, profile); } @@ -443,6 +455,26 @@ class DefaultTbContext implements TbContext { return entityActionMsg(entity, id, ruleNodeId, actionMsgType, null); } + @Deprecated(since = "3.5.2", forRemoval = true) + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action, K profile) { + try { + return entityActionMsg(id, getActionMetaData(ruleNodeId), JacksonUtil.toString(JacksonUtil.valueToTree(entity)), action, profile); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e); + } + } + + @Deprecated(since = "3.5.2", forRemoval = true) + private TbMsg entityActionMsg(I id, TbMsgMetaData msgMetaData, String msgData, String action, K profile) { + String defaultQueueName = null; + RuleChainId defaultRuleChainId = null; + if (profile != null) { + defaultQueueName = profile.getDefaultQueueName(); + defaultRuleChainId = profile.getDefaultRuleChainId(); + } + return TbMsg.newMsg(defaultQueueName, action, id, msgMetaData, msgData, defaultRuleChainId, null); + } + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, TbMsgType actionMsgType, K profile) { try { return entityActionMsg(id, getActionMetaData(ruleNodeId), JacksonUtil.toString(JacksonUtil.valueToTree(entity)), actionMsgType, profile); @@ -878,10 +910,17 @@ class DefaultTbContext implements TbContext { } private static String getFailureMessage(Throwable th) { - if (th == null) { - return null; + String failureMessage; + if (th != null) { + if (!StringUtils.isEmpty(th.getMessage())) { + failureMessage = th.getMessage(); + } else { + failureMessage = th.getClass().getSimpleName(); + } + } else { + failureMessage = null; } - return StringUtils.isNotEmpty(th.getMessage()) ? th.getMessage() : th.getClass().getSimpleName(); + return failureMessage; } private class SimpleTbQueueCallback implements TbQueueCallback { 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 be35e74321..fa47faf8aa 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 @@ -225,7 +225,9 @@ public interface TbContext { TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId); - // TODO: Does this changes the message? + @Deprecated(since = "3.5.2", forRemoval = true) + TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action); + TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, TbMsgType actionMsgType); TbMsg attributesUpdatedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List attributes); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java index d0c3e28ec1..1fb000d709 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java @@ -38,9 +38,17 @@ public abstract class TbAbstractExternalNode implements TbNode { protected void tellFailure(TbContext ctx, TbMsg tbMsg, Throwable t) { if (forceAck) { - ctx.enqueueForTellFailure(tbMsg.copyWithNewCtx(), t); + if (t == null) { + ctx.enqueueForTellNext(tbMsg.copyWithNewCtx(), TbNodeConnectionType.FAILURE); + } else { + ctx.enqueueForTellFailure(tbMsg.copyWithNewCtx(), t); + } } else { - ctx.tellFailure(tbMsg, t); + if (t == null) { + ctx.tellNext(tbMsg, TbNodeConnectionType.FAILURE); + } else { + ctx.tellFailure(tbMsg, t); + } } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 058a70fdea..0dccabff80 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -111,13 +111,9 @@ public class TbDeviceProfileNode implements TbNode { if (msg.checkType(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG)) { scheduleAlarmHarvesting(ctx, msg); harvestAlarms(ctx, System.currentTimeMillis()); - return; - } - if (msg.checkType(TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG)) { + } else if (msg.checkType(TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG)) { updateProfile(ctx, new DeviceProfileId(UUID.fromString(msg.getData()))); - return; - } - if (msg.checkType(TbMsgType.DEVICE_UPDATE_SELF_MSG)) { + } else if (msg.checkType(TbMsgType.DEVICE_UPDATE_SELF_MSG)) { JsonNode data = JacksonUtil.toJsonNode(msg.getData()); DeviceId deviceId = new DeviceId(UUID.fromString(data.get("deviceId").asText())); if (data.has("profileId")) { @@ -125,30 +121,28 @@ public class TbDeviceProfileNode implements TbNode { } else { removeDeviceState(deviceId); } - return; - } - if (EntityType.DEVICE.equals(originatorType)) { - DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); - if (msg.checkType(TbMsgType.ENTITY_UPDATED)) { - invalidateDeviceProfileCache(deviceId, msg.getData()); - ctx.tellSuccess(msg); - return; - } - if (msg.checkType(TbMsgType.ENTITY_DELETED)) { - removeDeviceState(deviceId); - ctx.tellSuccess(msg); - return; - } - DeviceState deviceState = getOrCreateDeviceState(ctx, deviceId, null, false); - if (deviceState == null) { - log.info("Device was not found! Most probably device [" + deviceId + "] has been removed from the database. Acknowledging msg."); - ctx.ack(msg); + } else { + if (EntityType.DEVICE.equals(originatorType)) { + DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); + if (msg.checkType(TbMsgType.ENTITY_UPDATED)) { + invalidateDeviceProfileCache(deviceId, msg.getData()); + ctx.tellSuccess(msg); + } else if (msg.checkType(TbMsgType.ENTITY_DELETED)) { + removeDeviceState(deviceId); + ctx.tellSuccess(msg); + } else { + DeviceState deviceState = getOrCreateDeviceState(ctx, deviceId, null, false); + if (deviceState != null) { + deviceState.process(ctx, msg); + } else { + log.info("Device was not found! Most probably device [" + deviceId + "] has been removed from the database. Acknowledging msg."); + ctx.ack(msg); + } + } } else { - deviceState.process(ctx, msg); + ctx.tellSuccess(msg); } - return; } - ctx.tellSuccess(msg); } @Override From ad847ff40c9f07916190f9eebbee95206b18fc01 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 3 Aug 2023 20:22:58 +0300 Subject: [PATCH 166/168] changed checkType and checkTypeOneOf to isTypeOf and isTypeOneOf --- .../thingsboard/server/common/msg/TbMsg.java | 6 +++--- .../TbCopyAttributesToEntityViewNode.java | 6 +++--- .../rule/engine/action/TbMsgCountNode.java | 2 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 2 +- .../deduplication/TbMsgDeduplicationNode.java | 2 +- .../rule/engine/delay/TbMsgDelayNode.java | 2 +- .../engine/edge/AbstractTbMsgPushNode.java | 14 +++++++------- .../rule/engine/mail/TbSendEmailNode.java | 2 +- .../engine/metadata/CalculateDeltaNode.java | 2 +- .../rule/engine/profile/DeviceState.java | 18 +++++++++--------- .../engine/profile/TbDeviceProfileNode.java | 10 +++++----- .../rule/engine/rpc/TbSendRPCRequestNode.java | 2 +- .../engine/telemetry/TbMsgAttributesNode.java | 2 +- .../engine/telemetry/TbMsgTimeseriesNode.java | 2 +- 14 files changed, 36 insertions(+), 36 deletions(-) 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 bec094b804..a987a4a253 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 @@ -468,13 +468,13 @@ public final class TbMsg implements Serializable { return ts; } - public boolean checkType(TbMsgType tbMsgType) { + public boolean isTypeOf(TbMsgType tbMsgType) { return tbMsgType != null && tbMsgType.name().equals(this.type); } - public boolean checkTypeOneOf(TbMsgType... types) { + public boolean isTypeOneOf(TbMsgType... types) { for (TbMsgType type : types) { - if (checkType(type)) { + if (isTypeOf(type)) { return true; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 8cd833ce66..c103dd4006 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -75,11 +75,11 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.checkTypeOneOf(ATTRIBUTES_UPDATED, ATTRIBUTES_DELETED, + if (msg.isTypeOneOf(ATTRIBUTES_UPDATED, ATTRIBUTES_DELETED, ACTIVITY_EVENT, INACTIVITY_EVENT, POST_ATTRIBUTES_REQUEST)) { if (!msg.getMetaData().getData().isEmpty()) { long now = System.currentTimeMillis(); - String scope = msg.checkType(POST_ATTRIBUTES_REQUEST) ? + String scope = msg.isTypeOf(POST_ATTRIBUTES_REQUEST) ? DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue(DataConstants.SCOPE); ListenableFuture> entityViewsFuture = @@ -91,7 +91,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { long startTime = entityView.getStartTimeMs(); long endTime = entityView.getEndTimeMs(); if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { - if (msg.checkType(ATTRIBUTES_DELETED)) { + if (msg.isTypeOf(ATTRIBUTES_DELETED)) { List attributes = new ArrayList<>(); for (JsonElement element : JsonParser.parseString(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray()) { if (element.isJsonPrimitive()) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index e43caf6bb8..021123a308 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -65,7 +65,7 @@ public class TbMsgCountNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.checkType(TbMsgType.MSG_COUNT_SELF_MSG) && msg.getId().equals(nextTickId)) { + if (msg.isTypeOf(TbMsgType.MSG_COUNT_SELF_MSG) && msg.getId().equals(nextTickId)) { JsonObject telemetryJson = new JsonObject(); telemetryJson.addProperty(this.telemetryPrefix + "_" + ctx.getServiceId(), messagesProcessed.longValue()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index cd32a44ea0..b31c98bc0f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -107,7 +107,7 @@ public class TbMsgGeneratorNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { log.trace("onMsg, config {}, msg {}", config, msg); - if (initialized.get() && msg.checkType(TbMsgType.GENERATOR_NODE_SELF_MSG) && msg.getId().equals(nextTickId)) { + if (initialized.get() && msg.isTypeOf(TbMsgType.GENERATOR_NODE_SELF_MSG) && msg.getId().equals(nextTickId)) { TbStopWatch sw = TbStopWatch.create(); withCallback(generate(ctx, msg), m -> { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index 1c0803770b..81bb3d6772 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -80,7 +80,7 @@ public class TbMsgDeduplicationNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - if (msg.checkType(TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG)) { + if (msg.isTypeOf(TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG)) { processDeduplication(ctx, msg.getOriginator()); } else { processOnRegularMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index d17415c1a6..5414cc2bbe 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -61,7 +61,7 @@ public class TbMsgDelayNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.checkType(TbMsgType.DELAY_TIMEOUT_SELF_MSG)) { + if (msg.isTypeOf(TbMsgType.DELAY_TIMEOUT_SELF_MSG)) { TbMsg pendingMsg = pendingMsgs.remove(UUID.fromString(msg.getData())); if (pendingMsg != null) { ctx.enqueueForTellNext( 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 3472ea011b..7888922dc6 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 @@ -82,7 +82,7 @@ public abstract class AbstractTbMsgPushNode Date: Fri, 4 Aug 2023 19:07:55 +0300 Subject: [PATCH 167/168] UI: Implement widget title font and color settings. --- .../src/app/core/services/dialog.service.ts | 5 +- .../alarms-table-basic-config.component.html | 22 ++++- .../alarms-table-basic-config.component.ts | 10 +++ .../basic/basic-widget-config.module.ts | 2 - ...entities-table-basic-config.component.html | 22 ++++- .../entities-table-basic-config.component.ts | 10 +++ .../simple-card-basic-config.component.ts | 2 +- ...meseries-table-basic-config.component.html | 22 ++++- ...timeseries-table-basic-config.component.ts | 10 +++ .../value-card-basic-config.component.ts | 2 +- .../chart/flot-basic-config.component.html | 22 ++++- .../chart/flot-basic-config.component.ts | 10 +++ .../config/widget-config-components.module.ts | 7 +- .../lib/cards/value-card-widget.component.ts | 2 +- .../lib/cards/value-card-widget.models.ts | 2 +- .../value-card-widget-settings.component.ts | 2 +- .../background-settings-panel.component.ts | 2 +- .../common/background-settings.component.ts | 2 +- .../common/color-settings-panel.component.ts | 2 +- .../common/color-settings.component.ts | 2 +- .../common/css-unit-select.component.html | 4 +- .../common/css-unit-select.component.ts | 7 +- .../common/date-format-select.component.ts | 2 +- .../date-format-settings-panel.component.ts | 2 +- .../common/font-settings-panel.component.html | 16 +++- .../common/font-settings-panel.component.ts | 43 +++++++--- .../common/font-settings.component.ts | 14 +++- .../common/widget-settings-common.module.ts | 84 +++++++++++++++++++ .../lib/settings/widget-settings.module.ts | 54 +----------- .../widget/widget-config.component.html | 20 ++++- .../widget/widget-config.component.ts | 8 ++ .../home/models/dashboard-component.models.ts | 13 +-- .../components/color-input.component.ts | 30 ++----- .../color-picker-panel.component.html | 8 ++ .../color-picker-panel.component.ts | 9 ++ .../dialog/color-picker-dialog.component.html | 1 + .../dialog/color-picker-dialog.component.ts | 3 + ui-ngx/src/app/shared/models/public-api.ts | 1 + .../models}/widget-settings.models.ts | 29 +++++-- ui-ngx/src/app/shared/models/widget.models.ts | 7 +- .../assets/locale/locale.constant-en_US.json | 3 +- ui-ngx/src/form.scss | 2 +- 42 files changed, 379 insertions(+), 141 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts rename ui-ngx/src/app/{modules/home/components/widget/config => shared/models}/widget-settings.models.ts (92%) diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts index 2ca2d6d87e..6ba2687278 100644 --- a/ui-ngx/src/app/core/services/dialog.service.ts +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -96,13 +96,14 @@ export class DialogService { return dialogRef.afterClosed(); } - colorPicker(color: string): Observable { + colorPicker(color: string, colorClearButton = false): Observable { return this.dialog.open(ColorPickerDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { - color + color, + colorClearButton }, autoFocus: false }).afterClosed(); diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html index ffddc9df59..f46686e0c0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html @@ -50,13 +50,24 @@
widget-config.card-appearance
-
+
{{ 'widget-config.card-title' | translate }} - - - +
+ + + + + + + +
@@ -68,6 +79,7 @@ formControlName="titleIcon">
@@ -84,12 +96,14 @@
{{ 'widget-config.text-color' | translate }}
{{ 'widget-config.background-color' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts index 33abc0670c..c67a59ac20 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts @@ -61,6 +61,8 @@ export class AlarmsTableBasicConfigComponent extends BasicWidgetConfigComponent columns: [this.getColumns(configData.config.alarmSource), []], showTitle: [configData.config.showTitle, []], title: [configData.config.settings?.alarmsTitle, []], + titleFont: [configData.config.titleFont, []], + titleColor: [configData.config.titleColor, []], showTitleIcon: [configData.config.showTitleIcon, []], titleIcon: [configData.config.titleIcon, []], iconColor: [configData.config.iconColor, []], @@ -82,6 +84,8 @@ export class AlarmsTableBasicConfigComponent extends BasicWidgetConfigComponent this.widgetConfig.config.showTitle = config.showTitle; this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; this.widgetConfig.config.settings.alarmsTitle = config.title; + this.widgetConfig.config.titleFont = config.titleFont; + this.widgetConfig.config.titleColor = config.titleColor; this.widgetConfig.config.showTitleIcon = config.showTitleIcon; this.widgetConfig.config.titleIcon = config.titleIcon; this.widgetConfig.config.iconColor = config.iconColor; @@ -100,6 +104,8 @@ export class AlarmsTableBasicConfigComponent extends BasicWidgetConfigComponent const showTitleIcon: boolean = this.alarmsTableWidgetConfigForm.get('showTitleIcon').value; if (showTitle) { this.alarmsTableWidgetConfigForm.get('title').enable(); + this.alarmsTableWidgetConfigForm.get('titleFont').enable(); + this.alarmsTableWidgetConfigForm.get('titleColor').enable(); this.alarmsTableWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false}); if (showTitleIcon) { this.alarmsTableWidgetConfigForm.get('titleIcon').enable(); @@ -110,11 +116,15 @@ export class AlarmsTableBasicConfigComponent extends BasicWidgetConfigComponent } } else { this.alarmsTableWidgetConfigForm.get('title').disable(); + this.alarmsTableWidgetConfigForm.get('titleFont').disable(); + this.alarmsTableWidgetConfigForm.get('titleColor').disable(); this.alarmsTableWidgetConfigForm.get('showTitleIcon').disable({emitEvent: false}); this.alarmsTableWidgetConfigForm.get('titleIcon').disable(); this.alarmsTableWidgetConfigForm.get('iconColor').disable(); } this.alarmsTableWidgetConfigForm.get('title').updateValueAndValidity({emitEvent}); + this.alarmsTableWidgetConfigForm.get('titleFont').updateValueAndValidity({emitEvent}); + this.alarmsTableWidgetConfigForm.get('titleColor').updateValueAndValidity({emitEvent}); this.alarmsTableWidgetConfigForm.get('showTitleIcon').updateValueAndValidity({emitEvent: false}); this.alarmsTableWidgetConfigForm.get('titleIcon').updateValueAndValidity({emitEvent}); this.alarmsTableWidgetConfigForm.get('iconColor').updateValueAndValidity({emitEvent}); 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 3a3425975b..795d46984c 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 @@ -34,7 +34,6 @@ import { TimeseriesTableBasicConfigComponent } from '@home/components/widget/config/basic/cards/timeseries-table-basic-config.component'; import { FlotBasicConfigComponent } from '@home/components/widget/config/basic/chart/flot-basic-config.component'; -import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widget-settings.module'; import { AlarmsTableBasicConfigComponent } from '@home/components/widget/config/basic/alarm/alarms-table-basic-config.component'; @@ -57,7 +56,6 @@ import { imports: [ CommonModule, SharedModule, - WidgetSettingsModule, WidgetConfigComponentsModule ], exports: [ diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html index c68caab86e..b4c2dbd616 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html @@ -39,13 +39,24 @@
widget-config.card-appearance
-
+
{{ 'widget-config.card-title' | translate }} - - - +
+ + + + + + + +
@@ -57,6 +68,7 @@ formControlName="titleIcon">
@@ -72,12 +84,14 @@
{{ 'widget-config.text-color' | translate }}
{{ 'widget-config.background-color' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts index 2061832918..f407293064 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts @@ -80,6 +80,8 @@ export class EntitiesTableBasicConfigComponent extends BasicWidgetConfigComponen columns: [this.getColumns(configData.config.datasources), []], showTitle: [configData.config.showTitle, []], title: [configData.config.settings?.entitiesTitle, []], + titleFont: [configData.config.titleFont, []], + titleColor: [configData.config.titleColor, []], showTitleIcon: [configData.config.showTitleIcon, []], titleIcon: [configData.config.titleIcon, []], iconColor: [configData.config.iconColor, []], @@ -100,6 +102,8 @@ export class EntitiesTableBasicConfigComponent extends BasicWidgetConfigComponen this.widgetConfig.config.showTitle = config.showTitle; this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; this.widgetConfig.config.settings.entitiesTitle = config.title; + this.widgetConfig.config.titleFont = config.titleFont; + this.widgetConfig.config.titleColor = config.titleColor; this.widgetConfig.config.showTitleIcon = config.showTitleIcon; this.widgetConfig.config.titleIcon = config.titleIcon; this.widgetConfig.config.iconColor = config.iconColor; @@ -118,6 +122,8 @@ export class EntitiesTableBasicConfigComponent extends BasicWidgetConfigComponen const showTitleIcon: boolean = this.entitiesTableWidgetConfigForm.get('showTitleIcon').value; if (showTitle) { this.entitiesTableWidgetConfigForm.get('title').enable(); + this.entitiesTableWidgetConfigForm.get('titleFont').enable(); + this.entitiesTableWidgetConfigForm.get('titleColor').enable(); this.entitiesTableWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false}); if (showTitleIcon) { this.entitiesTableWidgetConfigForm.get('titleIcon').enable(); @@ -128,11 +134,15 @@ export class EntitiesTableBasicConfigComponent extends BasicWidgetConfigComponen } } else { this.entitiesTableWidgetConfigForm.get('title').disable(); + this.entitiesTableWidgetConfigForm.get('titleFont').disable(); + this.entitiesTableWidgetConfigForm.get('titleColor').disable(); this.entitiesTableWidgetConfigForm.get('showTitleIcon').disable({emitEvent: false}); this.entitiesTableWidgetConfigForm.get('titleIcon').disable(); this.entitiesTableWidgetConfigForm.get('iconColor').disable(); } this.entitiesTableWidgetConfigForm.get('title').updateValueAndValidity({emitEvent}); + this.entitiesTableWidgetConfigForm.get('titleFont').updateValueAndValidity({emitEvent}); + this.entitiesTableWidgetConfigForm.get('titleColor').updateValueAndValidity({emitEvent}); this.entitiesTableWidgetConfigForm.get('showTitleIcon').updateValueAndValidity({emitEvent: false}); this.entitiesTableWidgetConfigForm.get('titleIcon').updateValueAndValidity({emitEvent}); this.entitiesTableWidgetConfigForm.get('iconColor').updateValueAndValidity({emitEvent}); diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts index 4238d6000c..283e39362a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts @@ -29,7 +29,7 @@ import { WidgetConfigComponent } from '@home/components/widget/widget-config.com import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component'; import { isUndefined } from '@core/utils'; -import { getLabel, setLabel } from '@home/components/widget/config/widget-settings.models'; +import { getLabel, setLabel } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-simple-card-basic-config', diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html index 49196fac3a..5952c206b6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html @@ -39,13 +39,24 @@
widget-config.card-appearance
-
+
{{ 'widget-config.card-title' | translate }} - - - +
+ + + + + + + +
@@ -57,6 +68,7 @@ formControlName="titleIcon">
@@ -72,12 +84,14 @@
{{ 'widget-config.text-color' | translate }}
{{ 'widget-config.background-color' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts index ac1b12167c..a8c4206fa7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts @@ -66,6 +66,8 @@ export class TimeseriesTableBasicConfigComponent extends BasicWidgetConfigCompon columns: [this.getColumns(configData.config.datasources), []], showTitle: [configData.config.showTitle, []], title: [configData.config.title, []], + titleFont: [configData.config.titleFont, []], + titleColor: [configData.config.titleColor, []], showTitleIcon: [configData.config.showTitleIcon, []], titleIcon: [configData.config.titleIcon, []], iconColor: [configData.config.iconColor, []], @@ -85,6 +87,8 @@ export class TimeseriesTableBasicConfigComponent extends BasicWidgetConfigCompon this.widgetConfig.config.actions = config.actions; 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.showTitleIcon; this.widgetConfig.config.titleIcon = config.titleIcon; this.widgetConfig.config.iconColor = config.iconColor; @@ -104,6 +108,8 @@ export class TimeseriesTableBasicConfigComponent extends BasicWidgetConfigCompon const showTitleIcon: boolean = this.timeseriesTableWidgetConfigForm.get('showTitleIcon').value; if (showTitle) { this.timeseriesTableWidgetConfigForm.get('title').enable(); + this.timeseriesTableWidgetConfigForm.get('titleFont').enable(); + this.timeseriesTableWidgetConfigForm.get('titleColor').enable(); this.timeseriesTableWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false}); if (showTitleIcon) { this.timeseriesTableWidgetConfigForm.get('titleIcon').enable(); @@ -114,11 +120,15 @@ export class TimeseriesTableBasicConfigComponent extends BasicWidgetConfigCompon } } else { this.timeseriesTableWidgetConfigForm.get('title').disable(); + this.timeseriesTableWidgetConfigForm.get('titleFont').disable(); + this.timeseriesTableWidgetConfigForm.get('titleColor').disable(); this.timeseriesTableWidgetConfigForm.get('showTitleIcon').disable({emitEvent: false}); this.timeseriesTableWidgetConfigForm.get('titleIcon').disable(); this.timeseriesTableWidgetConfigForm.get('iconColor').disable(); } this.timeseriesTableWidgetConfigForm.get('title').updateValueAndValidity({emitEvent}); + this.timeseriesTableWidgetConfigForm.get('titleFont').updateValueAndValidity({emitEvent}); + this.timeseriesTableWidgetConfigForm.get('titleColor').updateValueAndValidity({emitEvent}); this.timeseriesTableWidgetConfigForm.get('showTitleIcon').updateValueAndValidity({emitEvent: false}); this.timeseriesTableWidgetConfigForm.get('titleIcon').updateValueAndValidity({emitEvent}); this.timeseriesTableWidgetConfigForm.get('iconColor').updateValueAndValidity({emitEvent}); diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts index f00ec8e2e6..b535aaa7f8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts @@ -34,7 +34,7 @@ import { DateFormatSettings, getLabel, setLabel -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; import { valueCardDefaultSettings, ValueCardLayout, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html index 952b3f9031..0d607c230d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html @@ -39,13 +39,24 @@
widget-config.card-appearance
-
+
{{ 'widget-config.card-title' | translate }} - - - +
+ + + + + + + +
@@ -57,6 +68,7 @@ formControlName="titleIcon">
@@ -70,12 +82,14 @@
{{ 'widget-config.text-color' | translate }}
{{ 'widget-config.background-color' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts index 9d3dc4f1dd..c77d0ecb76 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts @@ -66,6 +66,8 @@ export class FlotBasicConfigComponent extends BasicWidgetConfigComponent { series: [this.getSeries(configData.config.datasources), []], showTitle: [configData.config.showTitle, []], title: [configData.config.title, []], + titleFont: [configData.config.titleFont, []], + titleColor: [configData.config.titleColor, []], showTitleIcon: [configData.config.showTitleIcon, []], titleIcon: [configData.config.titleIcon, []], iconColor: [configData.config.iconColor, []], @@ -89,6 +91,8 @@ export class FlotBasicConfigComponent extends BasicWidgetConfigComponent { this.widgetConfig.config.actions = config.actions; 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.showTitleIcon; this.widgetConfig.config.titleIcon = config.titleIcon; this.widgetConfig.config.iconColor = config.iconColor; @@ -114,6 +118,8 @@ export class FlotBasicConfigComponent extends BasicWidgetConfigComponent { const showLegend: boolean = this.flotWidgetConfigForm.get('showLegend').value; if (showTitle) { this.flotWidgetConfigForm.get('title').enable(); + this.flotWidgetConfigForm.get('titleFont').enable(); + this.flotWidgetConfigForm.get('titleColor').enable(); this.flotWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false}); if (showTitleIcon) { this.flotWidgetConfigForm.get('titleIcon').enable(); @@ -124,6 +130,8 @@ export class FlotBasicConfigComponent extends BasicWidgetConfigComponent { } } else { this.flotWidgetConfigForm.get('title').disable(); + this.flotWidgetConfigForm.get('titleFont').disable(); + this.flotWidgetConfigForm.get('titleColor').disable(); this.flotWidgetConfigForm.get('showTitleIcon').disable({emitEvent: false}); this.flotWidgetConfigForm.get('titleIcon').disable(); this.flotWidgetConfigForm.get('iconColor').disable(); @@ -134,6 +142,8 @@ export class FlotBasicConfigComponent extends BasicWidgetConfigComponent { this.flotWidgetConfigForm.get('legendConfig').disable(); } this.flotWidgetConfigForm.get('title').updateValueAndValidity({emitEvent}); + this.flotWidgetConfigForm.get('titleFont').updateValueAndValidity({emitEvent}); + this.flotWidgetConfigForm.get('titleColor').updateValueAndValidity({emitEvent}); this.flotWidgetConfigForm.get('showTitleIcon').updateValueAndValidity({emitEvent: false}); this.flotWidgetConfigForm.get('titleIcon').updateValueAndValidity({emitEvent}); this.flotWidgetConfigForm.get('iconColor').updateValueAndValidity({emitEvent}); 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 3777973eff..392a829955 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 @@ -29,6 +29,7 @@ import { FilterSelectComponent } from '@home/components/filter/filter-select.com import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widget-settings.module'; import { WidgetSettingsComponent } from '@home/components/widget/config/widget-settings.component'; import { TimewindowConfigPanelComponent } from '@home/components/widget/config/timewindow-config-panel.component'; +import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings/common/widget-settings-common.module'; @NgModule({ declarations: @@ -48,7 +49,8 @@ import { TimewindowConfigPanelComponent } from '@home/components/widget/config/t imports: [ CommonModule, SharedModule, - WidgetSettingsModule + WidgetSettingsModule, + WidgetSettingsCommonModule ], exports: [ AlarmAssigneeSelectComponent, @@ -61,7 +63,8 @@ import { TimewindowConfigPanelComponent } from '@home/components/widget/config/t EntityAliasSelectComponent, FilterSelectComponent, TimewindowConfigPanelComponent, - WidgetSettingsComponent + WidgetSettingsComponent, + WidgetSettingsCommonModule ] }) export class WidgetConfigComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts index f495581571..ac2cb62fee 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts @@ -28,7 +28,7 @@ import { iconStyle, overlayStyle, textStyle -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; import { valueCardDefaultSettings, ValueCardLayout, ValueCardWidgetSettings } from './value-card-widget.models'; import { WidgetComponent } from '@home/components/widget/widget.component'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts index 23d9a30329..549ba4abcd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts @@ -21,7 +21,7 @@ import { constantColor, cssUnit, DateFormatSettings, Font, lastUpdateAgoDateFormat -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; export enum ValueCardLayout { square = 'square', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts index 6f76546ac1..ad50dde8d2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts @@ -30,7 +30,7 @@ import { DateFormatProcessor, DateFormatSettings, getLabel -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-value-card-widget-settings', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts index 465d3383e2..ab94b95800 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts @@ -22,7 +22,7 @@ import { BackgroundSettings, BackgroundType, backgroundTypeTranslations, ComponentStyle -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { Store } from '@ngrx/store'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts index f8162575a3..62a2495942 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts @@ -22,7 +22,7 @@ import { BackgroundType, ComponentStyle, overlayStyle -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; import { MatButton } from '@angular/material/button'; import { TbPopoverService } from '@shared/components/popover.service'; import { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts index 2118bde6df..483674437d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts @@ -21,7 +21,7 @@ import { ColorSettings, ColorType, colorTypeTranslations -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { AbstractControl, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts index bffad41080..6d8ad1eefe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts @@ -16,7 +16,7 @@ import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ColorSettings, ColorType, ComponentStyle } from '@home/components/widget/config/widget-settings.models'; +import { ColorSettings, ColorType, ComponentStyle } from '@shared/models/widget-settings.models'; import { MatButton } from '@angular/material/button'; import { TbPopoverService } from '@shared/components/popover.service'; import { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.html index eaca1b6d40..1c00141b1d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.html @@ -16,7 +16,9 @@ --> - + + + {{ cssUnit }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts index dc593e9564..3d2658dae1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts @@ -16,7 +16,8 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms'; -import { cssUnit, cssUnits } from '@home/components/widget/config/widget-settings.models'; +import { cssUnit, cssUnits } from '@shared/models/widget-settings.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-css-unit-select', @@ -35,6 +36,10 @@ export class CssUnitSelectComponent implements OnInit, ControlValueAccessor { @Input() disabled: boolean; + @Input() + @coerceBoolean() + allowEmpty = false; + cssUnitsList = cssUnits; cssUnitFormControl: UntypedFormControl; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts index 413111ad1e..6393a92054 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts @@ -20,7 +20,7 @@ import { compareDateFormats, dateFormats, DateFormatSettings -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; import { MatButton } from '@angular/material/button'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.ts index 47f54fd5d5..be120b80f6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.ts @@ -16,7 +16,7 @@ import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; -import { DateFormatSettings } from '@home/components/widget/config/widget-settings.models'; +import { DateFormatSettings } from '@shared/models/widget-settings.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { UntypedFormControl, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html index 140c525ac2..36d488d766 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html @@ -51,7 +51,9 @@
widgets.widget-font.font-weight
- + + + {{ fontWeightTranslationsMap.has(weight) ? (fontWeightTranslationsMap.get(weight) | translate) : weight }} @@ -61,7 +63,9 @@
widgets.widget-font.font-style
- + + + {{ fontStyleTranslationsMap.get(style) | translate }} @@ -74,6 +78,14 @@
{{ previewText }}
+ +
diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts index 219ed0ec56..06d24f026b 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts @@ -23,6 +23,7 @@ import { DialogComponent } from '@shared/components/dialog.component'; export interface ColorPickerDialogData { color: string; + colorClearButton: boolean; } @Component({ @@ -33,6 +34,7 @@ export interface ColorPickerDialogData { export class ColorPickerDialogComponent extends DialogComponent { color: string; + colorClearButton: boolean; constructor(protected store: Store, protected router: Router, @@ -40,6 +42,7 @@ export class ColorPickerDialogComponent extends DialogComponent) { super(store, router, dialogRef); this.color = data.color; + this.colorClearButton = data.colorClearButton; } selectColor(color: string) { diff --git a/ui-ngx/src/app/shared/models/public-api.ts b/ui-ngx/src/app/shared/models/public-api.ts index aa93bece92..1b624259a9 100644 --- a/ui-ngx/src/app/shared/models/public-api.ts +++ b/ui-ngx/src/app/shared/models/public-api.ts @@ -54,6 +54,7 @@ export * from './settings.models'; export * from './tenant.model'; export * from './user.model'; export * from './user-settings.models'; +export * from './widget-settings.models'; export * from './widget.models'; export * from './widgets-bundle.model'; export * from './window-message.model'; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts b/ui-ngx/src/app/shared/models/widget-settings.models.ts similarity index 92% rename from ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts rename to ui-ngx/src/app/shared/models/widget-settings.models.ts index 0fa061de72..f7c1b6746e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -308,11 +308,30 @@ export const iconStyle = (size: number, sizeUnit: cssUnit): ComponentStyle => { }; }; -export const textStyle = (font: Font, lineHeight = '1.5', letterSpacing = '0.25px'): ComponentStyle => ({ - font: font.style + ' normal ' + font.weight + ' ' + (font.size+font.sizeUnit) + '/' + lineHeight + ' ' + font.family + - (font.family !== 'Roboto' ? ', Roboto' : ''), - letterSpacing -}); +export const textStyle = (font?: Font, lineHeight = '1.5', letterSpacing = '0.25px'): ComponentStyle => { + const style: ComponentStyle = { + lineHeight, + letterSpacing + }; + if (font?.style) { + style.fontStyle = font.style; + } + if (font?.weight) { + style.fontWeight = font.weight; + } + if (font?.size) { + style.fontSize = (font.size + (font.sizeUnit || 'px')); + } + if (font?.family) { + style.fontFamily = font.family + + (font.family !== 'Roboto' ? ', Roboto' : ''); + } + return style; +}; + +export const isFontSet = (font: Font): boolean => (!!font && !!font.style && !!font.weight && !!font.size && !!font.family); + +export const isFontPartiallySet = (font: Font): boolean => (!!font && (!!font.style || !!font.weight || !!font.size || !!font.family)); export const backgroundStyle = (background: BackgroundSettings): ComponentStyle => { if (background.type === BackgroundType.color) { diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index e0d9918540..c3b23048d1 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -40,6 +40,7 @@ import { Dashboard } from '@shared/models/dashboard.models'; import { IAliasController } from '@core/api/widget-api.models'; import { isEmptyStr } from '@core/utils'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { ComponentStyle, Font } from '@shared/models/widget-settings.models'; export enum widgetType { timeseries = 'timeseries', @@ -619,6 +620,8 @@ export enum WidgetConfigMode { export interface WidgetConfig { configMode?: WidgetConfigMode; title?: string; + titleFont?: Font; + titleColor?: string; titleIcon?: string; showTitle?: boolean; showTitleIcon?: boolean; @@ -639,9 +642,9 @@ export interface WidgetConfig { padding?: string; margin?: string; borderRadius?: string; - widgetStyle?: {[klass: string]: any}; + widgetStyle?: ComponentStyle; widgetCss?: string; - titleStyle?: {[klass: string]: any}; + titleStyle?: ComponentStyle; units?: string; decimals?: number; noDataDisplayMessage?: string; 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 7f0a271a04..026ebf857e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -74,7 +74,8 @@ "reset": "Reset", "show-more": "Show more", "dont-show-again": "Do not show again", - "see-documentation": "See documentation" + "see-documentation": "See documentation", + "clear": "Clear" }, "aggregation": { "aggregation": "Aggregation", diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 14711b85b4..bef8bf621e 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -21,7 +21,7 @@ flex-direction: column; align-items: stretch; gap: 12px; - padding: 12px 12px 12px 16px; + padding: 12px 7px 12px 16px; .mat-mdc-form-field, tb-unit-input { width: auto; &.medium-width { From 6bc9148f772e724f4ec322d9790665fa3d0c5050 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 7 Aug 2023 11:20:53 +0300 Subject: [PATCH 168/168] added optional nosxss validation for attribute/telemetry value --- .../DefaultTelemetrySubscriptionService.java | 6 ++++- .../src/main/resources/thingsboard.yml | 2 ++ .../controller/TelemetryControllerTest.java | 17 +++++++++++++ .../server/dao/attributes/AttributeUtils.java | 8 +++--- .../dao/attributes/BaseAttributesService.java | 8 ++++-- .../attributes/CachedAttributesService.java | 6 +++-- .../thingsboard/server/dao/util/KvUtils.java | 25 ++++++++++++------- 7 files changed, 54 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 3f5e52796a..df3a15e765 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; 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.common.util.ThingsBoardThreadFactory; @@ -78,6 +79,9 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer private ExecutorService tsCallBackExecutor; + @Value("${sql.ts.value_no_xss_validation:false}") + private boolean valueNoXssValidation; + public DefaultTelemetrySubscriptionService(AttributesService attrService, TimeseriesService tsService, @Lazy TbEntityViewService tbEntityViewService, @@ -135,7 +139,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer checkInternalEntity(entityId); boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null; if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { - KvUtils.validate(ts); + KvUtils.validate(ts, valueNoXssValidation); if (saveLatest) { saveAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback)); } else { diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index aa62a46d61..c8742921b0 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -269,11 +269,13 @@ sql: batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}" batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution + value_no_xss_validation: "${SQL_ATTRIBUTES_VALUE_NO_XSS_VALIDATION:false}" ts: batch_size: "${SQL_TS_BATCH_SIZE:10000}" batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}" batch_threads: "${SQL_TS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution + value_no_xss_validation: "${SQL_TS_VALUE_NO_XSS_VALIDATION:false}" ts_latest: batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}" batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}" diff --git a/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java index 47cac1b549..fc6fc33b8f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.controller; import org.junit.Test; +import org.springframework.test.context.TestPropertySource; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -25,6 +26,10 @@ import org.thingsboard.server.dao.service.DaoSqlTest; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @DaoSqlTest +@TestPropertySource(properties = { + "sql.attributes.value_no_xss_validation=true", + "sql.ts.value_no_xss_validation=true" +}) public class TelemetryControllerTest extends AbstractControllerTest { @Test @@ -39,6 +44,18 @@ public class TelemetryControllerTest extends AbstractControllerTest { doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody, String.class, status().isBadRequest()); } + @Test + public void testValueConstraintValidator() throws Exception { + loginTenantAdmin(); + Device device = createDevice(); + String correctRequestBody = "{\"data\": \"value\"}"; + doPostAsync("/api/plugins/telemetry/" + device.getId() + "/SHARED_SCOPE", correctRequestBody, String.class, status().isOk()); + doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", correctRequestBody, String.class, status().isOk()); + String invalidRequestBody = "{\"data\": \"alert(document)\\\">\"}"; + doPostAsync("/api/plugins/telemetry/" + device.getId() + "/SHARED_SCOPE", invalidRequestBody, String.class, status().isBadRequest()); + doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody, String.class, status().isBadRequest()); + } + private Device createDevice() throws Exception { String testToken = "TEST_TOKEN"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java index d1abeda5b6..192d56334d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java @@ -30,12 +30,12 @@ public class AttributeUtils { Validator.validateString(scope, "Incorrect scope " + scope); } - public static void validate(List kvEntries) { - kvEntries.forEach(AttributeUtils::validate); + public static void validate(List kvEntries, boolean valueNoXssValidation) { + kvEntries.forEach(tsKvEntry -> validate(tsKvEntry, valueNoXssValidation)); } - public static void validate(AttributeKvEntry kvEntry) { - KvUtils.validate(kvEntry); + public static void validate(AttributeKvEntry kvEntry, boolean valueNoXssValidation) { + KvUtils.validate(kvEntry, valueNoXssValidation); if (kvEntry.getDataType() == null) { throw new IncorrectParameterException("Incorrect kvEntry. Data type can't be null"); } else { diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java index 09414ac750..f855c116e2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.attributes; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; @@ -45,6 +46,9 @@ import static org.thingsboard.server.dao.attributes.AttributeUtils.validate; public class BaseAttributesService implements AttributesService { private final AttributesDao attributesDao; + @Value("${sql.attributes.value_no_xss_validation:false}") + private boolean valueNoXssValidation; + public BaseAttributesService(AttributesDao attributesDao) { this.attributesDao = attributesDao; } @@ -82,14 +86,14 @@ public class BaseAttributesService implements AttributesService { @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) { validate(entityId, scope); - AttributeUtils.validate(attribute); + AttributeUtils.validate(attribute, valueNoXssValidation); return attributesDao.save(tenantId, entityId, scope, attribute); } @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { validate(entityId, scope); - AttributeUtils.validate(attributes); + AttributeUtils.validate(attributes, valueNoXssValidation); List> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, scope, attribute)).collect(Collectors.toList()); return Futures.allAsList(saveFutures); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java index b95ce39d9a..faff81670b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java @@ -69,6 +69,8 @@ public class CachedAttributesService implements AttributesService { @Value("${cache.type:caffeine}") private String cacheType; + @Value("${sql.attributes.value_no_xss_validation:false}") + private boolean valueNoXssValidation; public CachedAttributesService(AttributesDao attributesDao, StatsFactory statsFactory, @@ -212,7 +214,7 @@ public class CachedAttributesService implements AttributesService { @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) { validate(entityId, scope); - AttributeUtils.validate(attribute); + AttributeUtils.validate(attribute, valueNoXssValidation); ListenableFuture future = attributesDao.save(tenantId, entityId, scope, attribute); return Futures.transform(future, key -> evict(entityId, scope, attribute, key), cacheExecutor); } @@ -220,7 +222,7 @@ public class CachedAttributesService implements AttributesService { @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { validate(entityId, scope); - AttributeUtils.validate(attributes); + AttributeUtils.validate(attributes, valueNoXssValidation); List> futures = new ArrayList<>(attributes.size()); for (var attribute : attributes) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java b/dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java index 788a19228b..e417a5a50a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.util; +import com.fasterxml.jackson.databind.JsonNode; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import org.thingsboard.server.common.data.kv.KvEntry; @@ -36,11 +37,11 @@ public class KvUtils { .maximumSize(100000).build(); } - public static void validate(List tsKvEntries) { - tsKvEntries.forEach(KvUtils::validate); + public static void validate(List tsKvEntries, boolean valueNoXssValidation) { + tsKvEntries.forEach(tsKvEntry -> validate(tsKvEntry, valueNoXssValidation)); } - public static void validate(KvEntry tsKvEntry) { + public static void validate(KvEntry tsKvEntry, boolean valueNoXssValidation) { if (tsKvEntry == null) { throw new IncorrectParameterException("Key value entry can't be null"); } @@ -55,14 +56,20 @@ public class KvUtils { throw new DataValidationException("Validation error: key length must be equal or less than 255"); } - if (validatedKeys.getIfPresent(key) != null) { - return; + if (validatedKeys.getIfPresent(key) == null) { + if (!NoXssValidator.isValid(key)) { + throw new DataValidationException("Validation error: key is malformed"); + } + validatedKeys.put(key, Boolean.TRUE); } - if (!NoXssValidator.isValid(key)) { - throw new DataValidationException("Validation error: key is malformed"); + if (valueNoXssValidation) { + Object value = tsKvEntry.getValue(); + if (value instanceof CharSequence || value instanceof JsonNode) { + if (!NoXssValidator.isValid(value.toString())) { + throw new DataValidationException("Validation error: value is malformed"); + } + } } - - validatedKeys.put(key, Boolean.TRUE); } }